open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636259__py3-none-any.whl

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 (296) hide show
  1. open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
  2. open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
  3. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
  6. swarm/agent/agent.py +49 -0
  7. swarm/auth.py +48 -113
  8. swarm/consumers.py +0 -19
  9. swarm/extensions/blueprint/__init__.py +16 -30
  10. swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
  11. swarm/extensions/blueprint/blueprint_base.py +458 -0
  12. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  13. swarm/extensions/blueprint/output_utils.py +95 -0
  14. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  15. swarm/extensions/cli/cli_args.py +0 -6
  16. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  17. swarm/extensions/cli/commands/config_management.py +6 -5
  18. swarm/extensions/cli/commands/edit_config.py +7 -16
  19. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  20. swarm/extensions/cli/commands/validate_env.py +4 -11
  21. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  22. swarm/extensions/cli/interactive_shell.py +2 -16
  23. swarm/extensions/config/config_loader.py +201 -107
  24. swarm/{core → extensions/config}/config_manager.py +38 -50
  25. swarm/{core → extensions/config}/server_config.py +0 -32
  26. swarm/extensions/launchers/build_launchers.py +14 -0
  27. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  28. swarm/extensions/launchers/swarm_api.py +64 -8
  29. swarm/extensions/launchers/swarm_cli.py +300 -8
  30. swarm/llm/chat_completion.py +195 -0
  31. swarm/serializers.py +5 -96
  32. swarm/settings.py +111 -99
  33. swarm/urls.py +74 -57
  34. swarm/utils/context_utils.py +4 -10
  35. swarm/utils/general_utils.py +0 -21
  36. swarm/utils/redact.py +36 -23
  37. swarm/views/api_views.py +39 -48
  38. swarm/views/chat_views.py +70 -237
  39. swarm/views/core_views.py +87 -80
  40. swarm/views/model_views.py +121 -64
  41. swarm/views/utils.py +441 -65
  42. swarm/views/web_views.py +2 -2
  43. open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
  44. open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
  45. open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
  46. swarm/blueprints/README.md +0 -68
  47. swarm/blueprints/blueprint_audit_status.json +0 -27
  48. swarm/blueprints/chatbot/README.md +0 -40
  49. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  50. swarm/blueprints/chatbot/metadata.json +0 -23
  51. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  52. swarm/blueprints/chucks_angels/README.md +0 -11
  53. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  54. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  55. swarm/blueprints/codey/CODEY.md +0 -15
  56. swarm/blueprints/codey/README.md +0 -115
  57. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  58. swarm/blueprints/codey/codey_cli.py +0 -373
  59. swarm/blueprints/codey/instructions.md +0 -17
  60. swarm/blueprints/codey/metadata.json +0 -23
  61. swarm/blueprints/common/operation_box_utils.py +0 -83
  62. swarm/blueprints/digitalbutlers/README.md +0 -11
  63. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  64. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  65. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  66. swarm/blueprints/divine_code/README.md +0 -3
  67. swarm/blueprints/divine_code/__init__.py +0 -10
  68. swarm/blueprints/divine_code/apps.py +0 -11
  69. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  70. swarm/blueprints/django_chat/apps.py +0 -6
  71. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  72. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  73. swarm/blueprints/django_chat/urls.py +0 -8
  74. swarm/blueprints/django_chat/views.py +0 -32
  75. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  76. swarm/blueprints/flock/README.md +0 -11
  77. swarm/blueprints/flock/__init__.py +0 -8
  78. swarm/blueprints/flock/blueprint_flock.py +0 -7
  79. swarm/blueprints/flock/test_basic.py +0 -3
  80. swarm/blueprints/geese/README.md +0 -10
  81. swarm/blueprints/geese/__init__.py +0 -8
  82. swarm/blueprints/geese/blueprint_geese.py +0 -384
  83. swarm/blueprints/geese/geese_cli.py +0 -102
  84. swarm/blueprints/jeeves/README.md +0 -41
  85. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  86. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  87. swarm/blueprints/jeeves/metadata.json +0 -24
  88. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  89. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  90. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  91. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  92. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  93. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  94. swarm/blueprints/poets/blueprint_poets.py +0 -546
  95. swarm/blueprints/poets/poets_cli.py +0 -23
  96. swarm/blueprints/rue_code/README.md +0 -8
  97. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  98. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  99. swarm/blueprints/stewie/apps.py +0 -12
  100. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  101. swarm/blueprints/stewie/models.py +0 -19
  102. swarm/blueprints/stewie/serializers.py +0 -10
  103. swarm/blueprints/stewie/settings.py +0 -17
  104. swarm/blueprints/stewie/urls.py +0 -11
  105. swarm/blueprints/stewie/views.py +0 -26
  106. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  107. swarm/blueprints/whinge_surf/README.md +0 -22
  108. swarm/blueprints/whinge_surf/__init__.py +0 -1
  109. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  110. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  111. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  112. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  113. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  114. swarm/blueprints/zeus/__init__.py +0 -2
  115. swarm/blueprints/zeus/apps.py +0 -4
  116. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  117. swarm/blueprints/zeus/zeus_cli.py +0 -13
  118. swarm/cli/async_input.py +0 -65
  119. swarm/cli/async_input_demo.py +0 -32
  120. swarm/core/blueprint_base.py +0 -769
  121. swarm/core/blueprint_discovery.py +0 -125
  122. swarm/core/blueprint_runner.py +0 -59
  123. swarm/core/blueprint_ux.py +0 -109
  124. swarm/core/build_launchers.py +0 -15
  125. swarm/core/cli/__init__.py +0 -1
  126. swarm/core/cli/commands/__init__.py +0 -1
  127. swarm/core/cli/commands/blueprint_management.py +0 -7
  128. swarm/core/cli/interactive_shell.py +0 -14
  129. swarm/core/cli/main.py +0 -50
  130. swarm/core/cli/utils/__init__.py +0 -1
  131. swarm/core/cli/utils/discover_commands.py +0 -18
  132. swarm/core/config_loader.py +0 -122
  133. swarm/core/output_utils.py +0 -193
  134. swarm/core/session_logger.py +0 -42
  135. swarm/core/slash_commands.py +0 -89
  136. swarm/core/swarm_api.py +0 -68
  137. swarm/core/swarm_cli.py +0 -216
  138. swarm/core/utils/__init__.py +0 -0
  139. swarm/extensions/blueprint/cli_handler.py +0 -197
  140. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  141. swarm/extensions/cli/utils/__init__.py +0 -1
  142. swarm/extensions/cli/utils/async_input.py +0 -46
  143. swarm/extensions/cli/utils/prompt_user.py +0 -3
  144. swarm/management/__init__.py +0 -0
  145. swarm/management/commands/__init__.py +0 -0
  146. swarm/management/commands/runserver.py +0 -58
  147. swarm/middleware.py +0 -65
  148. swarm/permissions.py +0 -38
  149. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  150. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  151. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  152. swarm/static/contrib/markedjs/marked.min.js +0 -6
  153. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  154. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  155. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  156. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  157. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  158. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  159. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  160. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  161. swarm/static/contrib/tabler-icons/download.svg +0 -21
  162. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  163. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  164. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  165. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  166. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  167. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  168. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  169. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  170. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  171. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  172. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  173. swarm/static/contrib/tabler-icons/message.svg +0 -21
  174. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  175. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  176. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  177. swarm/static/contrib/tabler-icons/search.svg +0 -19
  178. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  179. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  180. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  181. swarm/static/css/dropdown.css +0 -22
  182. swarm/static/htmx/htmx.min.js +0 -0
  183. swarm/static/js/dropdown.js +0 -23
  184. swarm/static/rest_mode/css/base.css +0 -470
  185. swarm/static/rest_mode/css/chat-history.css +0 -286
  186. swarm/static/rest_mode/css/chat.css +0 -251
  187. swarm/static/rest_mode/css/chatbot.css +0 -74
  188. swarm/static/rest_mode/css/chatgpt.css +0 -62
  189. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  190. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  191. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  192. swarm/static/rest_mode/css/general.css +0 -142
  193. swarm/static/rest_mode/css/layout.css +0 -167
  194. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  195. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  196. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  197. swarm/static/rest_mode/css/messages.css +0 -84
  198. swarm/static/rest_mode/css/messenger.css +0 -135
  199. swarm/static/rest_mode/css/settings.css +0 -91
  200. swarm/static/rest_mode/css/simple.css +0 -44
  201. swarm/static/rest_mode/css/slack.css +0 -58
  202. swarm/static/rest_mode/css/style.css +0 -156
  203. swarm/static/rest_mode/css/theme.css +0 -30
  204. swarm/static/rest_mode/css/toast.css +0 -40
  205. swarm/static/rest_mode/js/auth.js +0 -9
  206. swarm/static/rest_mode/js/blueprint.js +0 -41
  207. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  208. swarm/static/rest_mode/js/chatLogic.js +0 -79
  209. swarm/static/rest_mode/js/debug.js +0 -63
  210. swarm/static/rest_mode/js/events.js +0 -98
  211. swarm/static/rest_mode/js/main.js +0 -19
  212. swarm/static/rest_mode/js/messages.js +0 -264
  213. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  214. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  215. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  216. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  217. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  218. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  219. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  220. swarm/static/rest_mode/js/modules/state.js +0 -7
  221. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  222. swarm/static/rest_mode/js/modules/validation.js +0 -23
  223. swarm/static/rest_mode/js/rendering.js +0 -119
  224. swarm/static/rest_mode/js/settings.js +0 -130
  225. swarm/static/rest_mode/js/sidebar.js +0 -94
  226. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  227. swarm/static/rest_mode/js/slackLogic.js +0 -66
  228. swarm/static/rest_mode/js/splash.js +0 -76
  229. swarm/static/rest_mode/js/theme.js +0 -111
  230. swarm/static/rest_mode/js/toast.js +0 -36
  231. swarm/static/rest_mode/js/ui.js +0 -265
  232. swarm/static/rest_mode/js/validation.js +0 -57
  233. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  234. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  235. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  236. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  237. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  238. swarm/static/rest_mode/svg/attach.svg +0 -8
  239. swarm/static/rest_mode/svg/avatar.svg +0 -7
  240. swarm/static/rest_mode/svg/canvas.svg +0 -6
  241. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  242. swarm/static/rest_mode/svg/close.svg +0 -5
  243. swarm/static/rest_mode/svg/copy.svg +0 -4
  244. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  245. swarm/static/rest_mode/svg/edit.svg +0 -5
  246. swarm/static/rest_mode/svg/layout.svg +0 -9
  247. swarm/static/rest_mode/svg/logo.svg +0 -29
  248. swarm/static/rest_mode/svg/logout.svg +0 -5
  249. swarm/static/rest_mode/svg/mobile.svg +0 -5
  250. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  251. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  252. swarm/static/rest_mode/svg/plus.svg +0 -7
  253. swarm/static/rest_mode/svg/run_code.svg +0 -6
  254. swarm/static/rest_mode/svg/save.svg +0 -4
  255. swarm/static/rest_mode/svg/search.svg +0 -6
  256. swarm/static/rest_mode/svg/settings.svg +0 -4
  257. swarm/static/rest_mode/svg/speaker.svg +0 -5
  258. swarm/static/rest_mode/svg/stop.svg +0 -6
  259. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  260. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  261. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  262. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  263. swarm/static/rest_mode/svg/trash.svg +0 -10
  264. swarm/static/rest_mode/svg/undo.svg +0 -3
  265. swarm/static/rest_mode/svg/visible.svg +0 -8
  266. swarm/static/rest_mode/svg/voice.svg +0 -10
  267. swarm/templates/account/login.html +0 -22
  268. swarm/templates/account/signup.html +0 -32
  269. swarm/templates/base.html +0 -30
  270. swarm/templates/chat.html +0 -43
  271. swarm/templates/index.html +0 -35
  272. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  273. swarm/templates/rest_mode/components/header.html +0 -45
  274. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  275. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  276. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  277. swarm/templates/rest_mode/components/top_bar.html +0 -28
  278. swarm/templates/rest_mode/message_ui.html +0 -50
  279. swarm/templates/rest_mode/slackbot.html +0 -30
  280. swarm/templates/simple_blueprint_page.html +0 -24
  281. swarm/templates/websocket_partials/final_system_message.html +0 -3
  282. swarm/templates/websocket_partials/system_message.html +0 -4
  283. swarm/templates/websocket_partials/user_message.html +0 -5
  284. swarm/utils/ansi_box.py +0 -34
  285. swarm/utils/disable_tracing.py +0 -38
  286. swarm/utils/log_utils.py +0 -63
  287. swarm/utils/openai_patch.py +0 -33
  288. swarm/ux/ansi_box.py +0 -43
  289. swarm/ux/spinner.py +0 -53
  290. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
  291. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  292. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  293. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  294. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  295. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  296. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,114 +1,208 @@
1
- import json
1
+ """
2
+ Configuration Loader for Open Swarm MCP Framework.
3
+ """
4
+
2
5
  import os
3
- from pathlib import Path
6
+ import json
7
+ import re
4
8
  import logging
5
- from typing import Dict, Any, Optional
9
+ from typing import Any, Dict, List, Tuple, Optional
10
+ from pathlib import Path
11
+ from dotenv import load_dotenv
12
+ try: from .server_config import save_server_config
13
+ except ImportError: save_server_config = None
14
+
15
+ SWARM_DEBUG = os.getenv("SWARM_DEBUG", "False").lower() in ("true", "1", "yes")
16
+ try: from swarm.settings import BASE_DIR
17
+ except ImportError: BASE_DIR = Path(__file__).resolve().parent.parent.parent
18
+
19
+ from swarm.utils.redact import redact_sensitive_data
6
20
 
7
21
  logger = logging.getLogger(__name__)
22
+ logger.setLevel(logging.DEBUG if SWARM_DEBUG else logging.INFO)
8
23
 
9
- DEFAULT_CONFIG_FILENAME = "swarm_config.json"
10
-
11
- # --- find_config_file, load_config, save_config, validate_config, get_profile_from_config, _substitute_env_vars_recursive ---
12
- # (Keep these functions as they were)
13
- def find_config_file( specific_path: Optional[str]=None, start_dir: Optional[Path]=None, default_dir: Optional[Path]=None,) -> Optional[Path]:
14
- # 1. XDG config path
15
- xdg_config = Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / "swarm" / DEFAULT_CONFIG_FILENAME
16
- if xdg_config.is_file():
17
- logger.debug(f"Found config XDG: {xdg_config}")
18
- return xdg_config.resolve()
19
- # 2. User-specified path
20
- if specific_path:
21
- p = Path(specific_path)
22
- return p.resolve() if p.is_file() else logger.warning(f"Specified config path DNE: {specific_path}") or None # Fall through
23
- # 3. Upwards from start_dir
24
- if start_dir:
25
- current = start_dir.resolve()
26
- while current != current.parent:
27
- if (cp := current / DEFAULT_CONFIG_FILENAME).is_file():
28
- logger.debug(f"Found config upwards: {cp}")
29
- return cp.resolve()
30
- current = current.parent
31
- if (cp := current / DEFAULT_CONFIG_FILENAME).is_file():
32
- logger.debug(f"Found config at root: {cp}")
33
- return cp.resolve()
34
- # 4. Default dir
35
- if default_dir and (cp := default_dir.resolve() / DEFAULT_CONFIG_FILENAME).is_file():
36
- logger.debug(f"Found config default: {cp}")
37
- return cp.resolve()
38
- # 5. CWD
39
- cwd = Path.cwd()
40
- if start_dir is None or cwd != start_dir.resolve():
41
- if (cp := cwd / DEFAULT_CONFIG_FILENAME).is_file():
42
- logger.debug(f"Found config cwd: {cp}")
43
- return cp.resolve()
44
- logger.debug(f"Config '{DEFAULT_CONFIG_FILENAME}' not found.")
45
- return None
46
-
47
- def load_config(config_path: Path) -> Dict[str, Any]:
48
- logger.debug(f"Loading config from {config_path}")
24
+ # Add handler only if needed, DO NOT set handler level conditionally here
25
+ if not logger.handlers and not logging.getLogger().hasHandlers():
26
+ stream_handler = logging.StreamHandler()
27
+ formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s")
28
+ stream_handler.setFormatter(formatter)
29
+ logger.addHandler(stream_handler)
30
+ # --- REMOVED CONDITIONAL HANDLER LEVEL SETTING ---
31
+ # if not SWARM_DEBUG:
32
+ # stream_handler.setLevel(logging.WARNING)
33
+
34
+ config: Dict[str, Any] = {}
35
+ load_dotenv()
36
+ logger.debug("Environment variables potentially loaded from .env file.")
37
+
38
+ def process_config(config_dict: dict) -> dict:
39
+ """Processes config: resolves placeholders, merges external MCP."""
49
40
  try:
50
- with open(config_path, 'r') as f: config = json.load(f)
51
- logger.info(f"Loaded config from {config_path}"); validate_config(config); return config
52
- except FileNotFoundError: logger.error(f"Config DNE: {config_path}"); raise
53
- except json.JSONDecodeError as e: logger.error(f"JSON error {config_path}: {e}"); raise ValueError(f"Invalid JSON: {config_path}") from e
54
- except Exception as e: logger.error(f"Load error {config_path}: {e}"); raise
55
-
56
- def save_config(config: Dict[str, Any], config_path: Path):
57
- logger.info(f"Saving config to {config_path}")
58
- try: config_path.parent.mkdir(parents=True,exist_ok=True); f = config_path.open('w'); json.dump(config, f, indent=4); f.close(); logger.debug("Save OK.")
59
- except Exception as e: logger.error(f"Save failed {config_path}: {e}", exc_info=True); raise
60
-
61
- def validate_config(config: Dict[str, Any]):
62
- logger.debug("Validating config structure...")
63
- if "llm" not in config or not isinstance(config["llm"],dict): raise ValueError("Config 'llm' section missing/malformed.")
64
- for name, prof in config.get("llm",{}).items():
65
- if not isinstance(prof,dict): raise ValueError(f"LLM profile '{name}' not dict.")
66
- logger.debug("Config basic structure OK.")
67
-
68
- def get_profile_from_config(config: Dict[str, Any], profile_name: str) -> Dict[str, Any]:
69
- profile_data = config.get("llm", {}).get(profile_name)
70
- if profile_data is None: raise ValueError(f"LLM profile '{profile_name}' not found.")
71
- if not isinstance(profile_data, dict): raise ValueError(f"LLM profile '{profile_name}' not dict.")
72
- return _substitute_env_vars_recursive(profile_data)
73
-
74
- def _substitute_env_vars_recursive(data: Any) -> Any:
75
- if isinstance(data,dict): return {k:_substitute_env_vars_recursive(v) for k,v in data.items()}
76
- if isinstance(data,list): return [_substitute_env_vars_recursive(i) for i in data]
77
- if isinstance(data,str): return os.path.expandvars(data)
78
- return data
79
-
80
- def _substitute_env_vars(data: Any) -> Any:
81
- """Public API: Recursively substitute environment variables in dict, list, str."""
82
- return _substitute_env_vars_recursive(data)
83
-
84
- def create_default_config(config_path: Path):
85
- """Creates a default configuration file with valid JSON."""
86
- default_config = {
87
- "llm": {
88
- "default": {
89
- "provider": "openai",
90
- "model": "gpt-4o",
91
- "api_key": "${OPENAI_API_KEY}",
92
- "base_url": None,
93
- "description": "Default OpenAI profile. Requires OPENAI_API_KEY env var."
94
- },
95
- "ollama_example": {
96
- "provider": "ollama",
97
- "model": "llama3",
98
- "api_key": "ollama", # Usually not needed
99
- "base_url": "http://localhost:11434",
100
- "description": "Example for local Ollama Llama 3 model."
101
- }
102
- },
103
- "agents": {},
104
- "settings": {
105
- "default_markdown_output": True
106
- }
107
- }
108
- logger.info(f"Creating default configuration file at {config_path}")
41
+ resolved_config = resolve_placeholders(config_dict)
42
+ if logger.isEnabledFor(logging.DEBUG): logger.debug("Config after resolving placeholders: " + json.dumps(redact_sensitive_data(resolved_config), indent=2))
43
+ disable_merge = os.getenv("DISABLE_MCP_MERGE", "false").lower() in ("true", "1", "yes")
44
+ if not disable_merge:
45
+ if os.name == "nt": external_mcp_path = Path(os.getenv("APPDATA", Path.home())) / "Claude" / "claude_desktop_config.json"
46
+ else: external_mcp_path = Path.home() / ".vscode-server" / "data" / "User" / "globalStorage" / "rooveterinaryinc.roo-cline" / "settings" / "cline_mcp_settings.json"
47
+ if external_mcp_path.exists():
48
+ logger.info(f"Found external MCP settings file at: {external_mcp_path}")
49
+ try:
50
+ with open(external_mcp_path, "r", encoding='utf-8') as mcp_file: external_mcp_config = json.load(mcp_file)
51
+ if logger.isEnabledFor(logging.DEBUG): logger.debug("Loaded external MCP settings: " + json.dumps(redact_sensitive_data(external_mcp_config), indent=2))
52
+ main_mcp_servers = resolved_config.get("mcpServers", {}); external_mcp_servers = external_mcp_config.get("mcpServers", {})
53
+ merged_mcp_servers = main_mcp_servers.copy(); servers_added_count = 0
54
+ for name, server_cfg in external_mcp_servers.items():
55
+ if name not in merged_mcp_servers and not server_cfg.get("disabled", False): merged_mcp_servers[name] = server_cfg; servers_added_count += 1
56
+ if servers_added_count > 0: resolved_config["mcpServers"] = merged_mcp_servers; logger.info(f"Merged {servers_added_count} MCP servers.");
57
+ else: logger.debug("No new MCP servers added from external settings.")
58
+ except Exception as merge_err: logger.error(f"Failed to load/merge MCP settings from '{external_mcp_path}': {merge_err}", exc_info=logger.isEnabledFor(logging.DEBUG))
59
+ else: logger.debug(f"External MCP settings file not found at {external_mcp_path}. Skipping merge.")
60
+ else: logger.debug("MCP settings merge disabled.")
61
+ except Exception as e: logger.error(f"Failed during config processing: {e}", exc_info=logger.isEnabledFor(logging.DEBUG)); raise
62
+ globals()["config"] = resolved_config
63
+ return resolved_config
64
+
65
+ def resolve_placeholders(obj: Any) -> Any:
66
+ """Recursively resolve ${VAR_NAME} placeholders."""
67
+ if isinstance(obj, dict): return {k: resolve_placeholders(v) for k, v in obj.items()}
68
+ elif isinstance(obj, list): return [resolve_placeholders(item) for item in obj]
69
+ elif isinstance(obj, str):
70
+ pattern = re.compile(r'\$\{(\w+(?:[_-]\w+)*)\}')
71
+ resolved_string = obj; any_unresolved = False
72
+ for var_name in pattern.findall(obj):
73
+ env_value = os.getenv(var_name); placeholder = f'${{{var_name}}}'
74
+ if env_value is None:
75
+ log_level = logging.DEBUG
76
+ if resolved_string == placeholder:
77
+ log_level = logging.WARNING
78
+ resolved_string = None
79
+ any_unresolved = True
80
+ logger.log(log_level, f"Env var '{var_name}' not set for placeholder '{placeholder}'. Resolving to None.")
81
+ return None
82
+ else:
83
+ resolved_string = resolved_string.replace(placeholder, "")
84
+ any_unresolved = True
85
+ logger.log(log_level, f"Env var '{var_name}' not set for placeholder '{placeholder}'. Removing from string.")
86
+ else:
87
+ resolved_string = resolved_string.replace(placeholder, env_value)
88
+ if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Resolved placeholder '{placeholder}' using env var '{var_name}'.")
89
+ if any_unresolved and resolved_string is not None:
90
+ logger.debug(f"String '{obj}' contained unresolved placeholders. Result: '{resolved_string}'")
91
+ return resolved_string
92
+ else: return obj
93
+
94
+ def load_server_config(file_path: Optional[str] = None) -> dict:
95
+ """Loads, resolves, and merges server config from JSON file."""
96
+ config_path: Optional[Path] = None
97
+ if file_path:
98
+ path_obj = Path(file_path)
99
+ if path_obj.is_file(): config_path = path_obj; logger.info(f"Using provided config file path: {config_path}")
100
+ else: logger.warning(f"Provided path '{file_path}' not found. Searching standard locations.")
101
+ if not config_path:
102
+ standard_paths = [ Path.cwd() / "swarm_config.json", Path(BASE_DIR) / "swarm_config.json", Path.home() / ".swarm" / "swarm_config.json" ]
103
+ config_path = next((p for p in standard_paths if p.is_file()), None)
104
+ if not config_path: raise FileNotFoundError(f"Config file 'swarm_config.json' not found. Checked: {[str(p) for p in standard_paths]}")
105
+ logger.info(f"Using config file found at: {config_path}")
109
106
  try:
110
- save_config(default_config, config_path) # Use save_config to write valid JSON
111
- logger.debug("Default configuration file created successfully.")
112
- except Exception as e:
113
- logger.error(f"Failed to create default config file at {config_path}: {e}", exc_info=True)
114
- raise
107
+ raw_config = json.loads(config_path.read_text(encoding='utf-8'))
108
+ if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Raw config loaded: {redact_sensitive_data(raw_config)}")
109
+ processed_config = process_config(raw_config)
110
+ globals()["config"] = processed_config
111
+ logger.info(f"Config loaded and processed from {config_path}")
112
+ return processed_config
113
+ except json.JSONDecodeError as e: logger.critical(f"Invalid JSON in {config_path}: {e}"); raise ValueError(f"Invalid JSON") from e
114
+ except Exception as e: logger.critical(f"Failed to read/process config {config_path}: {e}"); raise ValueError("Failed to load/process config") from e
115
+
116
+ def load_llm_config(config_dict: Optional[Dict[str, Any]] = None, llm_name: Optional[str] = None) -> Dict[str, Any]:
117
+ """Loads, validates, and resolves API keys for a specific LLM profile."""
118
+ if config_dict is None:
119
+ global_config = globals().get("config")
120
+ if not global_config:
121
+ try: config_dict = load_server_config(); globals()["config"] = config_dict
122
+ except Exception as e: raise ValueError("Global config not loaded and no config_dict provided.") from e
123
+ else: config_dict = global_config
124
+
125
+ target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
126
+ logger.debug(f"LOAD_LLM: Loading profile: '{target_llm_name}'.")
127
+
128
+ resolved_config = resolve_placeholders(config_dict)
129
+ llm_profiles = resolved_config.get("llm", {})
130
+ if not isinstance(llm_profiles, dict): raise ValueError("'llm' section must be a dictionary.")
131
+
132
+ llm_config = llm_profiles.get(target_llm_name)
133
+ config_source = f"config file ('{target_llm_name}')"
134
+ logger.debug(f"LOAD_LLM: Initial lookup for '{target_llm_name}': {'Found' if llm_config else 'Missing'}")
135
+
136
+ if not llm_config:
137
+ logger.warning(f"LOAD_LLM: Config for '{target_llm_name}' not found. Generating fallback.")
138
+ config_source = "fallback generation"
139
+ fb_provider = os.getenv("DEFAULT_LLM_PROVIDER", "openai"); fb_model = os.getenv("DEFAULT_LLM_MODEL", "gpt-4o")
140
+ llm_config = { "provider": fb_provider, "model": fb_model, "api_key": None, "base_url": None }
141
+ logger.debug(f"LOAD_LLM: Generated fallback core config: {llm_config}")
142
+
143
+ if not isinstance(llm_config, dict): raise ValueError(f"LLM profile '{target_llm_name}' must be a dictionary.")
144
+
145
+ final_api_key = llm_config.get("api_key"); key_log_source = f"{config_source} (resolved)" if final_api_key else config_source
146
+ provider = llm_config.get("provider"); api_key_required = llm_config.get("api_key_required", True)
147
+ logger.debug(f"LOAD_LLM: Initial key from {key_log_source}: {'****' if final_api_key else 'None'}. Required={api_key_required}")
148
+
149
+ logger.debug(f"LOAD_LLM: Checking ENV vars for potential override.")
150
+ specific_env_var_name = f"{provider.upper()}_API_KEY" if provider else "PROVIDER_API_KEY"
151
+ common_fallback_var = "OPENAI_API_KEY"
152
+ specific_key_from_env = os.getenv(specific_env_var_name); fallback_key_from_env = os.getenv(common_fallback_var)
153
+ logger.debug(f"LOAD_LLM: Env Check: Specific ('{specific_env_var_name}')={'****' if specific_key_from_env else 'None'}, Fallback ('{common_fallback_var}')={'****' if fallback_key_from_env else 'None'}")
154
+
155
+ if specific_key_from_env:
156
+ if final_api_key != specific_key_from_env: logger.info(f"LOAD_LLM: Overriding key with env var '{specific_env_var_name}'.")
157
+ final_api_key = specific_key_from_env; key_log_source = f"env var '{specific_env_var_name}'"
158
+ elif fallback_key_from_env:
159
+ if not specific_key_from_env or specific_env_var_name == common_fallback_var:
160
+ if final_api_key != fallback_key_from_env: logger.info(f"LOAD_LLM: Overriding key with fallback env var '{common_fallback_var}'.")
161
+ final_api_key = fallback_key_from_env; key_log_source = f"env var '{common_fallback_var}'"
162
+ else: logger.debug(f"LOAD_LLM: Specific env key '{specific_env_var_name}' unset, NOT using fallback.")
163
+ else: logger.debug(f"LOAD_LLM: No relevant API key found in environment variables.")
164
+
165
+ key_is_still_missing_or_empty = final_api_key is None or (isinstance(final_api_key, str) and not final_api_key.strip())
166
+ logger.debug(f"LOAD_LLM: Key after env check: {'****' if final_api_key else 'None'}. Source: {key_log_source}. Still MissingOrEmpty={key_is_still_missing_or_empty}")
167
+
168
+ if key_is_still_missing_or_empty:
169
+ if api_key_required and not os.getenv("SUPPRESS_DUMMY_KEY"):
170
+ final_api_key = "sk-DUMMYKEY"; key_log_source = "dummy key"; logger.warning(f"LOAD_LLM: Applying dummy key for '{target_llm_name}'.")
171
+ elif api_key_required:
172
+ key_log_source = "MISSING - ERROR"; raise ValueError(f"Required API key for LLM profile '{target_llm_name}' is missing.")
173
+ else: key_log_source = "Not Required/Not Found"
174
+
175
+ final_llm_config = llm_config.copy(); final_llm_config["api_key"] = final_api_key; final_llm_config["_log_key_source"] = key_log_source
176
+ logger.debug(f"LOAD_LLM: Returning final config for '{target_llm_name}': {redact_sensitive_data(final_llm_config)}")
177
+ return final_llm_config
178
+
179
+ def get_llm_model(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> str:
180
+ target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
181
+ try: llm_config = load_llm_config(config_dict, target_llm_name)
182
+ except ValueError as e: raise ValueError(f"Could not load config for LLM '{target_llm_name}': {e}") from e
183
+ model_name = llm_config.get("model")
184
+ if not model_name or not isinstance(model_name, str): raise ValueError(f"'model' name missing/invalid for LLM '{target_llm_name}'.")
185
+ logger.debug(f"Retrieved model name '{model_name}' for LLM '{target_llm_name}'")
186
+ return model_name
187
+
188
+ def load_and_validate_llm(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> Dict[str, Any]:
189
+ target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
190
+ logger.debug(f"Loading and validating LLM (via load_llm_config) for profile: {target_llm_name}")
191
+ return load_llm_config(config_dict, target_llm_name)
192
+
193
+ def get_server_params(server_config: Dict[str, Any], server_name: str) -> Optional[Dict[str, Any]]:
194
+ """Extracts and validates parameters needed to start an MCP server."""
195
+ command = server_config.get("command"); args = server_config.get("args", []); config_env = server_config.get("env", {})
196
+ if not command: logger.error(f"MCP server '{server_name}' missing 'command'."); return None
197
+ if not isinstance(args, list): logger.error(f"MCP server '{server_name}' 'args' must be list."); return None
198
+ if not isinstance(config_env, dict): logger.error(f"MCP server '{server_name}' 'env' must be dict."); return None
199
+ env = {**os.environ.copy(), **config_env}; valid_env = {}
200
+ for k, v in env.items():
201
+ if v is None: logger.warning(f"Env var '{k}' for MCP server '{server_name}' resolved to None. Omitting.")
202
+ else: valid_env[k] = str(v)
203
+ return {"command": command, "args": args, "env": valid_env}
204
+
205
+ def list_mcp_servers(config_dict: Dict[str, Any]) -> List[str]:
206
+ """Returns a list of configured MCP server names."""
207
+ return list(config_dict.get("mcpServers", {}).keys())
208
+
@@ -6,11 +6,17 @@ import sys
6
6
  import logging
7
7
  from typing import Any, Dict
8
8
 
9
- from swarm.core.server_config import load_server_config, save_server_config
9
+ from swarm.extensions.config.config_loader import (
10
+ load_server_config,
11
+ resolve_placeholders
12
+ )
10
13
  from swarm.utils.color_utils import color_text
11
14
  from swarm.settings import DEBUG
12
- from swarm.core.utils.logger import *
13
- from swarm.extensions.cli.utils.prompt_user import prompt_user
15
+ from swarm.extensions.cli.utils import (
16
+ prompt_user,
17
+ log_and_exit,
18
+ display_message
19
+ )
14
20
 
15
21
  # Initialize logger for this module
16
22
  logger = logging.getLogger(__name__)
@@ -23,24 +29,6 @@ if not logger.handlers:
23
29
 
24
30
  CONFIG_BACKUP_SUFFIX = ".backup"
25
31
 
26
- def resolve_placeholders(config):
27
- # Recursively resolve placeholders in the config dict (env vars, etc.)
28
- import os
29
- import re
30
- pattern = re.compile(r'\$\{([^}]+)\}')
31
- def _resolve(val):
32
- if isinstance(val, str):
33
- def replacer(match):
34
- var = match.group(1)
35
- return os.environ.get(var, match.group(0))
36
- return pattern.sub(replacer, val)
37
- elif isinstance(val, dict):
38
- return {k: _resolve(v) for k, v in val.items()}
39
- elif isinstance(val, list):
40
- return [_resolve(v) for v in val]
41
- return val
42
- return _resolve(config)
43
-
44
32
  def backup_configuration(config_path: str) -> None:
45
33
  """
46
34
  Create a backup of the existing configuration file.
@@ -52,10 +40,10 @@ def backup_configuration(config_path: str) -> None:
52
40
  try:
53
41
  shutil.copy(config_path, backup_path)
54
42
  logger.info(f"Configuration backup created at '{backup_path}'")
55
- print(f"Backup of configuration created at '{backup_path}'")
43
+ display_message(f"Backup of configuration created at '{backup_path}'", "info")
56
44
  except Exception as e:
57
45
  logger.error(f"Failed to create configuration backup: {e}")
58
- print(f"Failed to create backup: {e}")
46
+ display_message(f"Failed to create backup: {e}", "error")
59
47
  sys.exit(1)
60
48
 
61
49
  def load_config(config_path: str) -> Dict[str, Any]:
@@ -78,11 +66,11 @@ def load_config(config_path: str) -> Dict[str, Any]:
78
66
  logger.debug(f"Raw configuration loaded: {config}")
79
67
  except FileNotFoundError:
80
68
  logger.error(f"Configuration file not found at {config_path}")
81
- print(f"Configuration file not found at {config_path}")
69
+ display_message(f"Configuration file not found at {config_path}", "error")
82
70
  sys.exit(1)
83
71
  except json.JSONDecodeError as e:
84
72
  logger.error(f"Invalid JSON in configuration file {config_path}: {e}")
85
- print(f"Invalid JSON in configuration file {config_path}: {e}")
73
+ display_message(f"Invalid JSON in configuration file {config_path}: {e}", "error")
86
74
  sys.exit(1)
87
75
 
88
76
  # Resolve placeholders recursively
@@ -91,7 +79,7 @@ def load_config(config_path: str) -> Dict[str, Any]:
91
79
  logger.debug(f"Configuration after resolving placeholders: {resolved_config}")
92
80
  except Exception as e:
93
81
  logger.error(f"Failed to resolve placeholders in configuration: {e}")
94
- print(f"Failed to resolve placeholders in configuration: {e}")
82
+ display_message(f"Failed to resolve placeholders in configuration: {e}", "error")
95
83
  sys.exit(1)
96
84
 
97
85
  return resolved_config
@@ -111,10 +99,10 @@ def save_config(config_path: str, config: Dict[str, Any]) -> None:
111
99
  with open(config_path, "w") as f:
112
100
  json.dump(config, f, indent=4)
113
101
  logger.info(f"Configuration saved to '{config_path}'")
114
- print(f"Configuration saved to '{config_path}'")
102
+ display_message(f"Configuration saved to '{config_path}'", "info")
115
103
  except Exception as e:
116
104
  logger.error(f"Failed to save configuration: {e}")
117
- print(f"Failed to save configuration: {e}")
105
+ display_message(f"Failed to save configuration: {e}", "error")
118
106
  sys.exit(1)
119
107
 
120
108
  def add_llm(config_path: str) -> None:
@@ -125,20 +113,20 @@ def add_llm(config_path: str) -> None:
125
113
  config_path (str): Path to the configuration file.
126
114
  """
127
115
  config = load_config(config_path)
128
- print("Starting the process to add a new LLM.")
116
+ display_message("Starting the process to add a new LLM.", "info")
129
117
 
130
118
  while True:
131
119
  llm_name = prompt_user("Enter the name of the new LLM (or type 'done' to finish)").strip()
132
- print(f"User entered LLM name: {llm_name}")
120
+ display_message(f"User entered LLM name: {llm_name}", "info")
133
121
  if llm_name.lower() == 'done':
134
- print("Finished adding LLMs.")
122
+ display_message("Finished adding LLMs.", "info")
135
123
  break
136
124
  if not llm_name:
137
- print("LLM name cannot be empty.")
125
+ display_message("LLM name cannot be empty.", "error")
138
126
  continue
139
127
 
140
128
  if llm_name in config.get("llm", {}):
141
- print(f"LLM '{llm_name}' already exists.")
129
+ display_message(f"LLM '{llm_name}' already exists.", "warning")
142
130
  continue
143
131
 
144
132
  llm = {}
@@ -154,16 +142,16 @@ def add_llm(config_path: str) -> None:
154
142
  temperature_input = prompt_user("Enter the temperature (e.g., 0.7)").strip()
155
143
  llm["temperature"] = float(temperature_input)
156
144
  except ValueError:
157
- print("Invalid temperature value. Using default 0.7.")
145
+ display_message("Invalid temperature value. Using default 0.7.", "warning")
158
146
  llm["temperature"] = 0.7
159
147
 
160
148
  config.setdefault("llm", {})[llm_name] = llm
161
149
  logger.info(f"Added LLM '{llm_name}' to configuration.")
162
- print(f"LLM '{llm_name}' added.")
150
+ display_message(f"LLM '{llm_name}' added.", "info")
163
151
 
164
152
  backup_configuration(config_path)
165
153
  save_config(config_path, config)
166
- print("LLM configuration process completed.")
154
+ display_message("LLM configuration process completed.", "info")
167
155
 
168
156
  def remove_llm(config_path: str, llm_name: str) -> None:
169
157
  """
@@ -176,18 +164,18 @@ def remove_llm(config_path: str, llm_name: str) -> None:
176
164
  config = load_config(config_path)
177
165
 
178
166
  if llm_name not in config.get("llm", {}):
179
- print(f"LLM '{llm_name}' does not exist.")
167
+ display_message(f"LLM '{llm_name}' does not exist.", "error")
180
168
  return
181
169
 
182
170
  confirm = prompt_user(f"Are you sure you want to remove LLM '{llm_name}'? (yes/no)").strip().lower()
183
171
  if confirm not in ['yes', 'y']:
184
- print("Operation cancelled.")
172
+ display_message("Operation cancelled.", "warning")
185
173
  return
186
174
 
187
175
  del config["llm"][llm_name]
188
176
  backup_configuration(config_path)
189
177
  save_config(config_path, config)
190
- print(f"LLM '{llm_name}' has been removed.")
178
+ display_message(f"LLM '{llm_name}' has been removed.", "info")
191
179
  logger.info(f"Removed LLM '{llm_name}' from configuration.")
192
180
 
193
181
  def add_mcp_server(config_path: str) -> None:
@@ -198,20 +186,20 @@ def add_mcp_server(config_path: str) -> None:
198
186
  config_path (str): Path to the configuration file.
199
187
  """
200
188
  config = load_config(config_path)
201
- print("Starting the process to add a new MCP server.")
189
+ display_message("Starting the process to add a new MCP server.", "info")
202
190
 
203
191
  while True:
204
192
  server_name = prompt_user("Enter the name of the new MCP server (or type 'done' to finish)").strip()
205
- print(f"User entered MCP server name: {server_name}")
193
+ display_message(f"User entered MCP server name: {server_name}", "info")
206
194
  if server_name.lower() == 'done':
207
- print("Finished adding MCP servers.")
195
+ display_message("Finished adding MCP servers.", "info")
208
196
  break
209
197
  if not server_name:
210
- print("Server name cannot be empty.")
198
+ display_message("Server name cannot be empty.", "error")
211
199
  continue
212
200
 
213
201
  if server_name in config.get("mcpServers", {}):
214
- print(f"MCP server '{server_name}' already exists.")
202
+ display_message(f"MCP server '{server_name}' already exists.", "warning")
215
203
  continue
216
204
 
217
205
  server = {}
@@ -222,7 +210,7 @@ def add_mcp_server(config_path: str) -> None:
222
210
  if not isinstance(server["args"], list):
223
211
  raise ValueError
224
212
  except ValueError:
225
- print("Invalid arguments format. Using an empty list.")
213
+ display_message("Invalid arguments format. Using an empty list.", "warning")
226
214
  server["args"] = []
227
215
 
228
216
  env_vars = {}
@@ -238,11 +226,11 @@ def add_mcp_server(config_path: str) -> None:
238
226
 
239
227
  config.setdefault("mcpServers", {})[server_name] = server
240
228
  logger.info(f"Added MCP server '{server_name}' to configuration.")
241
- print(f"MCP server '{server_name}' added.")
229
+ display_message(f"MCP server '{server_name}' added.", "info")
242
230
 
243
231
  backup_configuration(config_path)
244
232
  save_config(config_path, config)
245
- print("MCP server configuration process completed.")
233
+ display_message("MCP server configuration process completed.", "info")
246
234
 
247
235
  def remove_mcp_server(config_path: str, server_name: str) -> None:
248
236
  """
@@ -255,16 +243,16 @@ def remove_mcp_server(config_path: str, server_name: str) -> None:
255
243
  config = load_config(config_path)
256
244
 
257
245
  if server_name not in config.get("mcpServers", {}):
258
- print(f"MCP server '{server_name}' does not exist.")
246
+ display_message(f"MCP server '{server_name}' does not exist.", "error")
259
247
  return
260
248
 
261
249
  confirm = prompt_user(f"Are you sure you want to remove MCP server '{server_name}'? (yes/no)").strip().lower()
262
250
  if confirm not in ['yes', 'y']:
263
- print("Operation cancelled.")
251
+ display_message("Operation cancelled.", "warning")
264
252
  return
265
253
 
266
254
  del config["mcpServers"][server_name]
267
255
  backup_configuration(config_path)
268
256
  save_config(config_path, config)
269
- print(f"MCP server '{server_name}' has been removed.")
257
+ display_message(f"MCP server '{server_name}' has been removed.", "info")
270
258
  logger.info(f"Removed MCP server '{server_name}' from configuration.")
@@ -47,35 +47,3 @@ def save_server_config(config: dict, file_path: str = None) -> None:
47
47
  except OSError as e:
48
48
  logger.error(f"Error saving configuration to {file_path}: {e}")
49
49
  raise
50
-
51
- def load_server_config(file_path: str = None) -> dict:
52
- """
53
- Loads the server configuration from a JSON file.
54
-
55
- Args:
56
- file_path (str): The path to the configuration file. Defaults to 'swarm_settings.json' in the current directory.
57
-
58
- Returns:
59
- dict: The loaded configuration dictionary.
60
-
61
- Raises:
62
- FileNotFoundError: If the configuration file does not exist.
63
- ValueError: If the configuration is not a valid dictionary or JSON is invalid.
64
- """
65
- if file_path is None:
66
- file_path = os.path.join(os.getcwd(), "swarm_settings.json")
67
- logger.debug(f"Loading server configuration from {file_path}")
68
- try:
69
- with open(file_path, "r") as file:
70
- config = json.load(file)
71
- if not isinstance(config, dict):
72
- logger.error("Loaded configuration is not a dictionary.")
73
- raise ValueError("Configuration must be a dictionary.")
74
- logger.debug(f"Loaded configuration: {redact_sensitive_data(config)}")
75
- return config
76
- except FileNotFoundError as e:
77
- logger.error(f"Configuration file not found: {file_path}")
78
- raise
79
- except json.JSONDecodeError as e:
80
- logger.error(f"Invalid JSON in configuration file {file_path}: {e}")
81
- raise ValueError(f"Invalid JSON in configuration file: {e}")
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env python3
2
+ import PyInstaller.__main__
3
+
4
+ def build_executable(script, output_name):
5
+ PyInstaller.__main__.run([
6
+ script,
7
+ "--onefile",
8
+ "--name", output_name,
9
+ "--add-data", "swarm_config.json:." # Adjust if additional data is needed
10
+ ])
11
+
12
+ if __name__ == "__main__":
13
+ build_executable("launchers/swarm_cli.py", "swarm-cli")
14
+ build_executable("launchers/swarm_rest.py", "swarm-rest")