open-swarm 0.1.1744937124__py3-none-any.whl → 0.1.1744942852__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 (30) hide show
  1. {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/METADATA +1 -1
  2. {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/RECORD +11 -29
  3. swarm/blueprints/chatbot/blueprint_chatbot.py +13 -3
  4. swarm/blueprints/codey/blueprint_codey.py +188 -0
  5. swarm/blueprints/gaggle/blueprint_gaggle.py +21 -4
  6. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +33 -3
  7. swarm/extensions/blueprint/__init__.py +1 -2
  8. swarm/extensions/blueprint/cli_handler.py +20 -4
  9. swarm/extensions/blueprint/agent_utils.py +0 -21
  10. swarm/extensions/blueprint/blueprint_base.py +0 -333
  11. swarm/extensions/blueprint/blueprint_discovery.py +0 -128
  12. swarm/extensions/blueprint/blueprint_utils.py +0 -17
  13. swarm/extensions/blueprint/common_utils.py +0 -12
  14. swarm/extensions/blueprint/config_loader.py +0 -122
  15. swarm/extensions/blueprint/output_utils.py +0 -173
  16. swarm/extensions/blueprint/slash_commands.py +0 -17
  17. swarm/extensions/blueprint/spinner.py +0 -100
  18. swarm/extensions/config/config_manager.py +0 -258
  19. swarm/extensions/config/server_config.py +0 -49
  20. swarm/extensions/config/setup_wizard.py +0 -103
  21. swarm/extensions/config/utils/__init__.py +0 -0
  22. swarm/extensions/config/utils/logger.py +0 -36
  23. swarm/extensions/launchers/build_launchers.py +0 -14
  24. swarm/extensions/launchers/build_swarm_wrapper.py +0 -12
  25. swarm/extensions/launchers/swarm_api.py +0 -68
  26. swarm/extensions/launchers/swarm_cli.py +0 -216
  27. swarm/extensions/launchers/swarm_wrapper.py +0 -29
  28. {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/WHEEL +0 -0
  29. {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/entry_points.txt +0 -0
  30. {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/licenses/LICENSE +0 -0
@@ -1,173 +0,0 @@
1
- """
2
- Output utilities for Swarm blueprints.
3
- """
4
-
5
- import json
6
- import logging
7
- import os
8
- import sys
9
- from typing import List, Dict, Any
10
-
11
- # Optional import for markdown rendering
12
- try:
13
- from rich.markdown import Markdown
14
- from rich.console import Console
15
- from rich.panel import Panel
16
- from rich.text import Text
17
- from rich.rule import Rule
18
- RICH_AVAILABLE = True
19
- except ImportError:
20
- RICH_AVAILABLE = False
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
- def render_markdown(content: str) -> None:
25
- """Render markdown content using rich, if available."""
26
- # --- DEBUG PRINT ---
27
- print(f"\n[DEBUG render_markdown called with rich={RICH_AVAILABLE}]", flush=True)
28
- if not RICH_AVAILABLE:
29
- print(content, flush=True) # Fallback print with flush
30
- return
31
- console = Console()
32
- md = Markdown(content)
33
- console.print(md) # Rich handles flushing
34
-
35
- def ansi_box(title: str, content: str, color: str = "94", emoji: str = "🔎", border: str = "─", width: int = 70) -> str:
36
- """Return a string or Panel with ANSI box formatting for search/analysis results using Rich if available."""
37
- if RICH_AVAILABLE:
38
- console = Console()
39
- # Rich supports color names or hex, map color code to name
40
- color_map = {
41
- "94": "bright_blue",
42
- "96": "bright_cyan",
43
- "92": "bright_green",
44
- "93": "bright_yellow",
45
- "91": "bright_red",
46
- "95": "bright_magenta",
47
- "90": "grey82",
48
- }
49
- style = color_map.get(color, "bright_blue")
50
- panel = Panel(
51
- content,
52
- title=f"{emoji} {title} {emoji}",
53
- border_style=style,
54
- width=width
55
- )
56
- # Return the rendered panel as a string for testability
57
- with console.capture() as capture:
58
- console.print(panel)
59
- return capture.get()
60
- # Fallback: legacy manual ANSI box
61
- top = f"\033[{color}m{emoji} {border * (width - 4)} {emoji}\033[0m"
62
- mid_title = f"\033[{color}m│ {title.center(width - 6)} │\033[0m"
63
- lines = content.splitlines()
64
- boxed = [top, mid_title, top]
65
- for line in lines:
66
- boxed.append(f"\033[{color}m│\033[0m {line.ljust(width - 6)} \033[{color}m│\033[0m")
67
- boxed.append(top)
68
- return "\n".join(boxed)
69
-
70
- def print_search_box(title: str, content: str, color: str = "94", emoji: str = "🔎"):
71
- print(ansi_box(title, content, color=color, emoji=emoji))
72
-
73
- def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None) -> None:
74
- """Format and print messages, optionally rendering assistant content as markdown."""
75
- # --- DEBUG PRINT ---
76
- print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}]", flush=True)
77
-
78
- if spinner:
79
- spinner.stop()
80
- sys.stdout.write("\r\033[K") # Clear spinner line
81
- sys.stdout.flush()
82
-
83
- if not messages:
84
- logger.debug("No messages to print in pretty_print_response.")
85
- return
86
-
87
- for i, msg in enumerate(messages):
88
- # --- DEBUG PRINT ---
89
- print(f"\n[DEBUG Processing message {i}: type={type(msg)}]", flush=True)
90
- if not isinstance(msg, dict):
91
- print(f"[DEBUG Skipping non-dict message {i}]", flush=True)
92
- continue
93
-
94
- role = msg.get("role")
95
- sender = msg.get("sender", role if role else "Unknown")
96
- msg_content = msg.get("content")
97
- tool_calls = msg.get("tool_calls")
98
- # --- DEBUG PRINT ---
99
- print(f"[DEBUG Message {i}: role={role}, sender={sender}, has_content={bool(msg_content)}, has_tools={bool(tool_calls)}]", flush=True)
100
-
101
-
102
- if role == "assistant":
103
- print(f"\033[94m{sender}\033[0m: ", end="", flush=True)
104
- if msg_content:
105
- # --- DEBUG PRINT ---
106
- print(f"\n[DEBUG Assistant content found, printing/rendering... Rich={RICH_AVAILABLE}, Markdown={use_markdown}]", flush=True)
107
- if use_markdown and RICH_AVAILABLE:
108
- render_markdown(msg_content)
109
- else:
110
- # --- DEBUG PRINT ---
111
- print(f"\n[DEBUG Using standard print for content:]", flush=True)
112
- print(msg_content, flush=True) # Added flush
113
- elif not tool_calls:
114
- print(flush=True) # Flush newline if no content/tools
115
-
116
- if tool_calls and isinstance(tool_calls, list):
117
- print(" \033[92mTool Calls:\033[0m", flush=True)
118
- for tc in tool_calls:
119
- if not isinstance(tc, dict): continue
120
- func = tc.get("function", {})
121
- tool_name = func.get("name", "Unnamed Tool")
122
- args_str = func.get("arguments", "{}")
123
- try: args_obj = json.loads(args_str); args_pretty = ", ".join(f"{k}={v!r}" for k, v in args_obj.items())
124
- except json.JSONDecodeError: args_pretty = args_str
125
- print(f" \033[95m{tool_name}\033[0m({args_pretty})", flush=True)
126
-
127
- elif role == "tool":
128
- tool_name = msg.get("tool_name", msg.get("name", "tool"))
129
- tool_id = msg.get("tool_call_id", "N/A")
130
- try: content_obj = json.loads(msg_content); pretty_content = json.dumps(content_obj, indent=2)
131
- except (json.JSONDecodeError, TypeError): pretty_content = msg_content
132
- print(f" \033[93m[{tool_name} Result ID: {tool_id}]\033[0m:\n {pretty_content.replace(chr(10), chr(10) + ' ')}", flush=True)
133
- else:
134
- # --- DEBUG PRINT ---
135
- print(f"[DEBUG Skipping message {i} with role '{role}']", flush=True)
136
-
137
- def print_terminal_command_result(cmd: str, result: dict, max_lines: int = 10):
138
- """
139
- Render a terminal command result in the CLI with a shell prompt emoji, header, and Rich box.
140
- - Header: 🐚 Ran terminal command
141
- - Top line: colored, [basename(pwd)] > [cmd]
142
- - Output: Rich Panel, max 10 lines, tailing if longer, show hint for toggle
143
- """
144
- if not RICH_AVAILABLE:
145
- # Fallback to simple print
146
- print(f"🐚 Ran terminal command\n[{os.path.basename(result['cwd'])}] > {cmd}")
147
- lines = result['output'].splitlines()
148
- if len(lines) > max_lines:
149
- lines = lines[-max_lines:]
150
- print("[Output truncated. Showing last 10 lines.]")
151
- print("\n".join(lines))
152
- return
153
-
154
- console = Console()
155
- cwd_base = os.path.basename(result['cwd'])
156
- header = Text(f"🐚 Ran terminal command", style="bold yellow")
157
- subheader = Rule(f"[{cwd_base}] > {cmd}", style="bright_black")
158
- lines = result['output'].splitlines()
159
- truncated = False
160
- if len(lines) > max_lines:
161
- lines = lines[-max_lines:]
162
- truncated = True
163
- output_body = "\n".join(lines)
164
- panel = Panel(
165
- output_body,
166
- title="Output",
167
- border_style="cyan",
168
- subtitle="[Output truncated. Showing last 10 lines. Press [t] to expand.]" if truncated else "",
169
- width=80
170
- )
171
- console.print(header)
172
- console.print(subheader)
173
- console.print(panel)
@@ -1,17 +0,0 @@
1
- # Minimal slash_commands.py to restore compatibility
2
-
3
- class SlashCommandRegistry:
4
- def __init__(self):
5
- self.commands = {}
6
- def register(self, command, func=None):
7
- if func is None:
8
- def decorator(f):
9
- self.commands[command] = f
10
- return f
11
- return decorator
12
- self.commands[command] = func
13
- return func
14
- def get(self, command):
15
- return self.commands.get(command)
16
-
17
- slash_registry = SlashCommandRegistry()
@@ -1,100 +0,0 @@
1
- """
2
- Simple terminal spinner for interactive feedback during long operations.
3
- """
4
-
5
- import os
6
- import sys
7
- import threading
8
- import time
9
- from typing import Optional
10
-
11
- class Spinner:
12
- """Simple terminal spinner for interactive feedback."""
13
- # Define spinner characters (can be customized)
14
- SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
15
- # Custom status sequences for special cases
16
- STATUS_SEQUENCES = {
17
- 'generating': ['Generating.', 'Generating..', 'Generating...'],
18
- 'running': ['Running...']
19
- }
20
-
21
- def __init__(self, interactive: bool, custom_sequence: str = None):
22
- """
23
- Initialize the spinner.
24
-
25
- Args:
26
- interactive (bool): Hint whether the environment is interactive.
27
- Spinner is disabled if False or if output is not a TTY.
28
- custom_sequence (str): Optional name for a custom status sequence (e.g., 'generating', 'running').
29
- """
30
- self.interactive = interactive
31
- self.is_tty = sys.stdout.isatty()
32
- self.enabled = self.interactive and self.is_tty
33
- self.running = False
34
- self.thread: Optional[threading.Thread] = None
35
- self.status = ""
36
- self.index = 0
37
- self.custom_sequence = custom_sequence
38
- self.sequence_idx = 0
39
-
40
- def start(self, status: str = "Processing..."):
41
- """Start the spinner with an optional status message."""
42
- if not self.enabled or self.running:
43
- return # Do nothing if disabled or already running
44
- self.status = status
45
- self.running = True
46
- self.sequence_idx = 0
47
- self.thread = threading.Thread(target=self._spin, daemon=True)
48
- self.thread.start()
49
-
50
- def stop(self):
51
- """Stop the spinner and clear the line."""
52
- if not self.enabled or not self.running:
53
- return # Do nothing if disabled or not running
54
- self.running = False
55
- if self.thread is not None:
56
- self.thread.join() # Wait for the thread to finish
57
- sys.stdout.write("\r\033[K")
58
- sys.stdout.flush()
59
- self.thread = None
60
-
61
- def _spin(self):
62
- """Internal method running in the spinner thread to animate."""
63
- start_time = time.time()
64
- warned = False
65
- while self.running:
66
- elapsed = time.time() - start_time
67
- if self.custom_sequence and self.custom_sequence in self.STATUS_SEQUENCES:
68
- seq = self.STATUS_SEQUENCES[self.custom_sequence]
69
- # If taking longer than 10s, show special message
70
- if elapsed > 10 and not warned:
71
- msg = f"{seq[-1]} Taking longer than expected"
72
- warned = True
73
- else:
74
- msg = seq[self.sequence_idx % len(seq)]
75
- sys.stdout.write(f"\r{msg}\033[K")
76
- sys.stdout.flush()
77
- self.sequence_idx += 1
78
- else:
79
- char = self.SPINNER_CHARS[self.index % len(self.SPINNER_CHARS)]
80
- sys.stdout.write(f"\r{char} {self.status}\033[K")
81
- sys.stdout.flush()
82
- self.index += 1
83
- time.sleep(0.4 if self.custom_sequence else 0.1)
84
-
85
- # Example usage (if run directly)
86
- if __name__ == "__main__":
87
- print("Starting spinner test...")
88
- s = Spinner(interactive=True) # Assume interactive for testing
89
- s.start("Doing something cool")
90
- try:
91
- time.sleep(5) # Simulate work
92
- s.stop()
93
- print("Spinner stopped.")
94
- s.start("Doing another thing")
95
- time.sleep(3)
96
- except KeyboardInterrupt:
97
- print("\nInterrupted.")
98
- finally:
99
- s.stop() # Ensure spinner stops on exit/error
100
- print("Test finished.")
@@ -1,258 +0,0 @@
1
- # src/swarm/extensions/config/config_manager.py
2
-
3
- import json
4
- import shutil
5
- import sys
6
- import logging
7
- from typing import Any, Dict
8
-
9
- from swarm.extensions.config.config_loader import (
10
- load_server_config,
11
- resolve_placeholders
12
- )
13
- from swarm.utils.color_utils import color_text
14
- from swarm.settings import DEBUG
15
- from swarm.extensions.cli.utils import (
16
- prompt_user,
17
- log_and_exit,
18
- display_message
19
- )
20
-
21
- # Initialize logger for this module
22
- logger = logging.getLogger(__name__)
23
- logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
24
- if not logger.handlers:
25
- stream_handler = logging.StreamHandler()
26
- formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
27
- stream_handler.setFormatter(formatter)
28
- logger.addHandler(stream_handler)
29
-
30
- CONFIG_BACKUP_SUFFIX = ".backup"
31
-
32
- def backup_configuration(config_path: str) -> None:
33
- """
34
- Create a backup of the existing configuration file.
35
-
36
- Args:
37
- config_path (str): Path to the configuration file.
38
- """
39
- backup_path = config_path + CONFIG_BACKUP_SUFFIX
40
- try:
41
- shutil.copy(config_path, backup_path)
42
- logger.info(f"Configuration backup created at '{backup_path}'")
43
- display_message(f"Backup of configuration created at '{backup_path}'", "info")
44
- except Exception as e:
45
- logger.error(f"Failed to create configuration backup: {e}")
46
- display_message(f"Failed to create backup: {e}", "error")
47
- sys.exit(1)
48
-
49
- def load_config(config_path: str) -> Dict[str, Any]:
50
- """
51
- Load the server configuration from a JSON file and resolve placeholders.
52
-
53
- Args:
54
- config_path (str): Path to the configuration file.
55
-
56
- Returns:
57
- Dict[str, Any]: The resolved configuration.
58
-
59
- Raises:
60
- FileNotFoundError: If the configuration file does not exist.
61
- ValueError: If the file contains invalid JSON or unresolved placeholders.
62
- """
63
- try:
64
- with open(config_path, "r") as file:
65
- config = json.load(file)
66
- logger.debug(f"Raw configuration loaded: {config}")
67
- except FileNotFoundError:
68
- logger.error(f"Configuration file not found at {config_path}")
69
- display_message(f"Configuration file not found at {config_path}", "error")
70
- sys.exit(1)
71
- except json.JSONDecodeError as e:
72
- logger.error(f"Invalid JSON in configuration file {config_path}: {e}")
73
- display_message(f"Invalid JSON in configuration file {config_path}: {e}", "error")
74
- sys.exit(1)
75
-
76
- # Resolve placeholders recursively
77
- try:
78
- resolved_config = resolve_placeholders(config)
79
- logger.debug(f"Configuration after resolving placeholders: {resolved_config}")
80
- except Exception as e:
81
- logger.error(f"Failed to resolve placeholders in configuration: {e}")
82
- display_message(f"Failed to resolve placeholders in configuration: {e}", "error")
83
- sys.exit(1)
84
-
85
- return resolved_config
86
-
87
- def save_config(config_path: str, config: Dict[str, Any]) -> None:
88
- """
89
- Save the updated configuration to the config file.
90
-
91
- Args:
92
- config_path (str): Path to the configuration file.
93
- config (Dict[str, Any]): Configuration dictionary to save.
94
-
95
- Raises:
96
- SystemExit: If saving the configuration fails.
97
- """
98
- try:
99
- with open(config_path, "w") as f:
100
- json.dump(config, f, indent=4)
101
- logger.info(f"Configuration saved to '{config_path}'")
102
- display_message(f"Configuration saved to '{config_path}'", "info")
103
- except Exception as e:
104
- logger.error(f"Failed to save configuration: {e}")
105
- display_message(f"Failed to save configuration: {e}", "error")
106
- sys.exit(1)
107
-
108
- def add_llm(config_path: str) -> None:
109
- """
110
- Add a new LLM to the configuration.
111
-
112
- Args:
113
- config_path (str): Path to the configuration file.
114
- """
115
- config = load_config(config_path)
116
- display_message("Starting the process to add a new LLM.", "info")
117
-
118
- while True:
119
- llm_name = prompt_user("Enter the name of the new LLM (or type 'done' to finish)").strip()
120
- display_message(f"User entered LLM name: {llm_name}", "info")
121
- if llm_name.lower() == 'done':
122
- display_message("Finished adding LLMs.", "info")
123
- break
124
- if not llm_name:
125
- display_message("LLM name cannot be empty.", "error")
126
- continue
127
-
128
- if llm_name in config.get("llm", {}):
129
- display_message(f"LLM '{llm_name}' already exists.", "warning")
130
- continue
131
-
132
- llm = {}
133
- llm["provider"] = prompt_user("Enter the provider type (e.g., 'openai', 'ollama')").strip()
134
- llm["model"] = prompt_user("Enter the model name (e.g., 'gpt-4')").strip()
135
- llm["base_url"] = prompt_user("Enter the base URL for the API").strip()
136
- llm_api_key_input = prompt_user("Enter the environment variable for the API key (e.g., 'OPENAI_API_KEY') or leave empty if not required").strip()
137
- if llm_api_key_input:
138
- llm["api_key"] = f"${{{llm_api_key_input}}}"
139
- else:
140
- llm["api_key"] = ""
141
- try:
142
- temperature_input = prompt_user("Enter the temperature (e.g., 0.7)").strip()
143
- llm["temperature"] = float(temperature_input)
144
- except ValueError:
145
- display_message("Invalid temperature value. Using default 0.7.", "warning")
146
- llm["temperature"] = 0.7
147
-
148
- config.setdefault("llm", {})[llm_name] = llm
149
- logger.info(f"Added LLM '{llm_name}' to configuration.")
150
- display_message(f"LLM '{llm_name}' added.", "info")
151
-
152
- backup_configuration(config_path)
153
- save_config(config_path, config)
154
- display_message("LLM configuration process completed.", "info")
155
-
156
- def remove_llm(config_path: str, llm_name: str) -> None:
157
- """
158
- Remove an existing LLM from the configuration.
159
-
160
- Args:
161
- config_path (str): Path to the configuration file.
162
- llm_name (str): Name of the LLM to remove.
163
- """
164
- config = load_config(config_path)
165
-
166
- if llm_name not in config.get("llm", {}):
167
- display_message(f"LLM '{llm_name}' does not exist.", "error")
168
- return
169
-
170
- confirm = prompt_user(f"Are you sure you want to remove LLM '{llm_name}'? (yes/no)").strip().lower()
171
- if confirm not in ['yes', 'y']:
172
- display_message("Operation cancelled.", "warning")
173
- return
174
-
175
- del config["llm"][llm_name]
176
- backup_configuration(config_path)
177
- save_config(config_path, config)
178
- display_message(f"LLM '{llm_name}' has been removed.", "info")
179
- logger.info(f"Removed LLM '{llm_name}' from configuration.")
180
-
181
- def add_mcp_server(config_path: str) -> None:
182
- """
183
- Add a new MCP server to the configuration.
184
-
185
- Args:
186
- config_path (str): Path to the configuration file.
187
- """
188
- config = load_config(config_path)
189
- display_message("Starting the process to add a new MCP server.", "info")
190
-
191
- while True:
192
- server_name = prompt_user("Enter the name of the new MCP server (or type 'done' to finish)").strip()
193
- display_message(f"User entered MCP server name: {server_name}", "info")
194
- if server_name.lower() == 'done':
195
- display_message("Finished adding MCP servers.", "info")
196
- break
197
- if not server_name:
198
- display_message("Server name cannot be empty.", "error")
199
- continue
200
-
201
- if server_name in config.get("mcpServers", {}):
202
- display_message(f"MCP server '{server_name}' already exists.", "warning")
203
- continue
204
-
205
- server = {}
206
- server["command"] = prompt_user("Enter the command to run the MCP server (e.g., 'npx', 'uvx')").strip()
207
- args_input = prompt_user("Enter the arguments as a JSON array (e.g., [\"-y\", \"server-name\"])").strip()
208
- try:
209
- server["args"] = json.loads(args_input)
210
- if not isinstance(server["args"], list):
211
- raise ValueError
212
- except ValueError:
213
- display_message("Invalid arguments format. Using an empty list.", "warning")
214
- server["args"] = []
215
-
216
- env_vars = {}
217
- add_env = prompt_user("Do you want to add environment variables? (yes/no)").strip().lower()
218
- while add_env in ['yes', 'y']:
219
- env_var = prompt_user("Enter the environment variable name").strip()
220
- env_value = prompt_user(f"Enter the value or placeholder for '{env_var}' (e.g., '${{{env_var}_KEY}}')").strip()
221
- if env_var and env_value:
222
- env_vars[env_var] = env_value
223
- add_env = prompt_user("Add another environment variable? (yes/no)").strip().lower()
224
-
225
- server["env"] = env_vars
226
-
227
- config.setdefault("mcpServers", {})[server_name] = server
228
- logger.info(f"Added MCP server '{server_name}' to configuration.")
229
- display_message(f"MCP server '{server_name}' added.", "info")
230
-
231
- backup_configuration(config_path)
232
- save_config(config_path, config)
233
- display_message("MCP server configuration process completed.", "info")
234
-
235
- def remove_mcp_server(config_path: str, server_name: str) -> None:
236
- """
237
- Remove an existing MCP server from the configuration.
238
-
239
- Args:
240
- config_path (str): Path to the configuration file.
241
- server_name (str): Name of the MCP server to remove.
242
- """
243
- config = load_config(config_path)
244
-
245
- if server_name not in config.get("mcpServers", {}):
246
- display_message(f"MCP server '{server_name}' does not exist.", "error")
247
- return
248
-
249
- confirm = prompt_user(f"Are you sure you want to remove MCP server '{server_name}'? (yes/no)").strip().lower()
250
- if confirm not in ['yes', 'y']:
251
- display_message("Operation cancelled.", "warning")
252
- return
253
-
254
- del config["mcpServers"][server_name]
255
- backup_configuration(config_path)
256
- save_config(config_path, config)
257
- display_message(f"MCP server '{server_name}' has been removed.", "info")
258
- logger.info(f"Removed MCP server '{server_name}' from configuration.")
@@ -1,49 +0,0 @@
1
- """
2
- Module for managing server configuration files, including saving and validation.
3
-
4
- Provides utilities to save configurations to disk and ensure integrity of data.
5
- """
6
-
7
- import json
8
- import os
9
- import logging
10
- from swarm.utils.redact import redact_sensitive_data
11
-
12
- # Initialize logger for this module
13
- logger = logging.getLogger(__name__)
14
- DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "t") # Define DEBUG locally
15
- logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
16
- stream_handler = logging.StreamHandler()
17
- formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
18
- stream_handler.setFormatter(formatter)
19
- if not logger.handlers:
20
- logger.addHandler(stream_handler)
21
-
22
- def save_server_config(config: dict, file_path: str = None) -> None:
23
- """
24
- Saves the server configuration to a JSON file.
25
-
26
- Args:
27
- config (dict): The configuration dictionary to save.
28
- file_path (str): The path to save the configuration file. Defaults to 'swarm_settings.json' in the current directory.
29
-
30
- Raises:
31
- ValueError: If the configuration is not a valid dictionary.
32
- OSError: If there are issues writing to the file.
33
- """
34
- if not isinstance(config, dict):
35
- logger.error("Provided configuration is not a dictionary.")
36
- raise ValueError("Configuration must be a dictionary.")
37
-
38
- if file_path is None:
39
- file_path = os.path.join(os.getcwd(), "swarm_settings.json")
40
-
41
- logger.debug(f"Saving server configuration to {file_path}")
42
- try:
43
- with open(file_path, "w") as file:
44
- json.dump(config, file, indent=4)
45
- logger.info(f"Configuration successfully saved to {file_path}")
46
- logger.debug(f"Saved configuration: {redact_sensitive_data(config)}")
47
- except OSError as e:
48
- logger.error(f"Error saving configuration to {file_path}: {e}")
49
- raise