open-swarm 0.1.1743070217__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 (89) hide show
  1. open_swarm-0.1.1743070217.dist-info/METADATA +258 -0
  2. open_swarm-0.1.1743070217.dist-info/RECORD +89 -0
  3. open_swarm-0.1.1743070217.dist-info/WHEEL +5 -0
  4. open_swarm-0.1.1743070217.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1743070217.dist-info/licenses/LICENSE +21 -0
  6. open_swarm-0.1.1743070217.dist-info/top_level.txt +1 -0
  7. swarm/__init__.py +3 -0
  8. swarm/agent/__init__.py +7 -0
  9. swarm/agent/agent.py +49 -0
  10. swarm/apps.py +53 -0
  11. swarm/auth.py +56 -0
  12. swarm/consumers.py +141 -0
  13. swarm/core.py +326 -0
  14. swarm/extensions/__init__.py +1 -0
  15. swarm/extensions/blueprint/__init__.py +36 -0
  16. swarm/extensions/blueprint/agent_utils.py +45 -0
  17. swarm/extensions/blueprint/blueprint_base.py +562 -0
  18. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  19. swarm/extensions/blueprint/blueprint_utils.py +17 -0
  20. swarm/extensions/blueprint/common_utils.py +12 -0
  21. swarm/extensions/blueprint/django_utils.py +203 -0
  22. swarm/extensions/blueprint/interactive_mode.py +102 -0
  23. swarm/extensions/blueprint/modes/rest_mode.py +37 -0
  24. swarm/extensions/blueprint/output_utils.py +95 -0
  25. swarm/extensions/blueprint/spinner.py +91 -0
  26. swarm/extensions/cli/__init__.py +0 -0
  27. swarm/extensions/cli/blueprint_runner.py +251 -0
  28. swarm/extensions/cli/cli_args.py +88 -0
  29. swarm/extensions/cli/commands/__init__.py +0 -0
  30. swarm/extensions/cli/commands/blueprint_management.py +31 -0
  31. swarm/extensions/cli/commands/config_management.py +15 -0
  32. swarm/extensions/cli/commands/edit_config.py +77 -0
  33. swarm/extensions/cli/commands/list_blueprints.py +22 -0
  34. swarm/extensions/cli/commands/validate_env.py +57 -0
  35. swarm/extensions/cli/commands/validate_envvars.py +39 -0
  36. swarm/extensions/cli/interactive_shell.py +41 -0
  37. swarm/extensions/cli/main.py +36 -0
  38. swarm/extensions/cli/selection.py +43 -0
  39. swarm/extensions/cli/utils/discover_commands.py +32 -0
  40. swarm/extensions/cli/utils/env_setup.py +15 -0
  41. swarm/extensions/cli/utils.py +105 -0
  42. swarm/extensions/config/__init__.py +6 -0
  43. swarm/extensions/config/config_loader.py +208 -0
  44. swarm/extensions/config/config_manager.py +258 -0
  45. swarm/extensions/config/server_config.py +49 -0
  46. swarm/extensions/config/setup_wizard.py +103 -0
  47. swarm/extensions/config/utils/__init__.py +0 -0
  48. swarm/extensions/config/utils/logger.py +36 -0
  49. swarm/extensions/launchers/__init__.py +1 -0
  50. swarm/extensions/launchers/build_launchers.py +14 -0
  51. swarm/extensions/launchers/build_swarm_wrapper.py +12 -0
  52. swarm/extensions/launchers/swarm_api.py +68 -0
  53. swarm/extensions/launchers/swarm_cli.py +304 -0
  54. swarm/extensions/launchers/swarm_wrapper.py +29 -0
  55. swarm/extensions/mcp/__init__.py +1 -0
  56. swarm/extensions/mcp/cache_utils.py +36 -0
  57. swarm/extensions/mcp/mcp_client.py +341 -0
  58. swarm/extensions/mcp/mcp_constants.py +7 -0
  59. swarm/extensions/mcp/mcp_tool_provider.py +110 -0
  60. swarm/llm/chat_completion.py +195 -0
  61. swarm/messages.py +132 -0
  62. swarm/migrations/0010_initial_chat_models.py +51 -0
  63. swarm/migrations/__init__.py +0 -0
  64. swarm/models.py +45 -0
  65. swarm/repl/__init__.py +1 -0
  66. swarm/repl/repl.py +87 -0
  67. swarm/serializers.py +12 -0
  68. swarm/settings.py +189 -0
  69. swarm/tool_executor.py +239 -0
  70. swarm/types.py +126 -0
  71. swarm/urls.py +89 -0
  72. swarm/util.py +124 -0
  73. swarm/utils/color_utils.py +40 -0
  74. swarm/utils/context_utils.py +272 -0
  75. swarm/utils/general_utils.py +162 -0
  76. swarm/utils/logger.py +61 -0
  77. swarm/utils/logger_setup.py +25 -0
  78. swarm/utils/message_sequence.py +173 -0
  79. swarm/utils/message_utils.py +95 -0
  80. swarm/utils/redact.py +68 -0
  81. swarm/views/__init__.py +41 -0
  82. swarm/views/api_views.py +46 -0
  83. swarm/views/chat_views.py +76 -0
  84. swarm/views/core_views.py +118 -0
  85. swarm/views/message_views.py +40 -0
  86. swarm/views/model_views.py +135 -0
  87. swarm/views/utils.py +457 -0
  88. swarm/views/web_views.py +149 -0
  89. swarm/wsgi.py +16 -0
@@ -0,0 +1,49 @@
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
@@ -0,0 +1,103 @@
1
+ # src/swarm/extensions/config/setup_wizard.py
2
+
3
+ import os
4
+ import json
5
+ from typing import Dict, Any
6
+
7
+ def run_setup_wizard(config_path: str, blueprints_metadata: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
8
+ """
9
+ Runs the interactive setup wizard.
10
+
11
+ Args:
12
+ config_path (str): Path to the configuration file.
13
+ blueprints_metadata (Dict[str, Dict[str, Any]]): Metadata for available blueprints.
14
+
15
+ Returns:
16
+ Dict[str, Any]: Updated configuration.
17
+ """
18
+ config = {
19
+ "llm_providers": {},
20
+ "mcpServers": {}
21
+ }
22
+
23
+ # Configure LLM settings
24
+ print("Configuring LLM providers:")
25
+ while True:
26
+ provider_name = input("Enter the name of the LLM provider to add (e.g., 'openai', 'ollama'), or type 'done' to finish: ").strip()
27
+ if provider_name.lower() in ['done', 'exit', 'quit']:
28
+ break
29
+ if not provider_name:
30
+ print("Provider name cannot be empty.")
31
+ continue
32
+ if provider_name in config["llm_providers"]:
33
+ print(f"LLM provider '{provider_name}' already exists.")
34
+ continue
35
+
36
+ provider = {}
37
+ provider["provider"] = input("Enter the provider identifier (e.g., 'openai', 'ollama'): ").strip()
38
+ provider["model"] = input("Enter the model name (e.g., 'gpt-4'): ").strip()
39
+ provider["base_url"] = input("Enter the base URL for the API (e.g., 'https://api.openai.com/v1'): ").strip()
40
+ provider["api_key"] = input("Enter the environment variable for the API key (e.g., 'OPENAI_API_KEY') [Leave empty if not required]: ").strip()
41
+ temperature_input = input("Enter the temperature for the model (e.g., 0.7): ").strip()
42
+ try:
43
+ provider["temperature"] = float(temperature_input)
44
+ except ValueError:
45
+ print("Invalid temperature value. Using default 0.7.")
46
+ provider["temperature"] = 0.7
47
+
48
+ config["llm_providers"][provider_name] = provider
49
+ print(f"LLM provider '{provider_name}' added successfully.\n")
50
+
51
+ # Select a default LLM provider
52
+ if config["llm_providers"]:
53
+ print("\nAvailable LLM Providers:")
54
+ for idx, provider in enumerate(config["llm_providers"].keys(), start=1):
55
+ print(f"{idx}. {provider}")
56
+ while True:
57
+ try:
58
+ default_choice = input("Enter the number of the LLM provider to set as default: ").strip()
59
+ default_choice = int(default_choice)
60
+ if 1 <= default_choice <= len(config["llm_providers"]):
61
+ default_provider = list(config["llm_providers"].keys())[default_choice - 1]
62
+ config["llm_providers"]["default"] = config["llm_providers"][default_provider]
63
+ print(f"Default LLM provider set to '{default_provider}'.\n")
64
+ break
65
+ else:
66
+ print(f"Please enter a number between 1 and {len(config['llm_providers'])}.")
67
+ except ValueError:
68
+ print("Invalid input. Please enter a valid number.")
69
+ else:
70
+ print("No LLM providers configured.")
71
+
72
+ # Select a blueprint
73
+ print("\nAvailable Blueprints:")
74
+ for idx, (key, metadata) in enumerate(blueprints_metadata.items(), start=1):
75
+ print(f"{idx}. {key}: {metadata.get('title', 'No title')} - {metadata.get('description', 'No description')}")
76
+
77
+ while True:
78
+ try:
79
+ blueprint_choice = input("\nEnter the number of the blueprint to use (0 to skip): ").strip()
80
+ if blueprint_choice.lower() in ["q", "quit", "exit"]: # Handle exit inputs
81
+ print("Exiting blueprint selection.")
82
+ break
83
+
84
+ blueprint_choice = int(blueprint_choice)
85
+ if blueprint_choice == 0:
86
+ print("No blueprint selected.")
87
+ break
88
+ elif 1 <= blueprint_choice <= len(blueprints_metadata):
89
+ selected_blueprint_key = list(blueprints_metadata.keys())[blueprint_choice - 1]
90
+ config["blueprint"] = selected_blueprint_key
91
+ print(f"Selected Blueprint: {selected_blueprint_key}\n")
92
+ break
93
+ else:
94
+ print(f"Invalid selection. Please enter a number between 0 and {len(blueprints_metadata)}.")
95
+ except ValueError:
96
+ print("Invalid input. Please enter a valid number.")
97
+
98
+ # Save configuration
99
+ with open(config_path, "w") as config_file:
100
+ json.dump(config, config_file, indent=4)
101
+ print(f"Configuration saved to {config_path}.\n")
102
+
103
+ return config
File without changes
@@ -0,0 +1,36 @@
1
+ # src/swarm/config/utils/logger.py
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ def setup_logger(name: str, level: int = logging.DEBUG, log_file: Optional[str] = None) -> logging.Logger:
7
+ """
8
+ Sets up and returns a logger with the specified name and level.
9
+
10
+ Args:
11
+ name (str): The name of the logger.
12
+ level (int, optional): Logging level. Defaults to logging.DEBUG.
13
+ log_file (str, optional): File path to log to. If None, logs to console. Defaults to None.
14
+
15
+ Returns:
16
+ logging.Logger: Configured logger instance.
17
+ """
18
+ logger = logging.getLogger(name)
19
+ logger.setLevel(level)
20
+
21
+ # Prevent adding multiple handlers to the logger
22
+ if not logger.handlers:
23
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
24
+
25
+ if log_file:
26
+ file_handler = logging.FileHandler(log_file)
27
+ file_handler.setLevel(level)
28
+ file_handler.setFormatter(formatter)
29
+ logger.addHandler(file_handler)
30
+
31
+ console_handler = logging.StreamHandler()
32
+ console_handler.setLevel(level)
33
+ console_handler.setFormatter(formatter)
34
+ logger.addHandler(console_handler)
35
+
36
+ return logger
@@ -0,0 +1 @@
1
+ # This file makes the launchers folder a Python package.
@@ -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")
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ import PyInstaller.__main__
3
+
4
+ PyInstaller.__main__.run([
5
+ "swarm_wrapper.py",
6
+ "--onefile",
7
+ "--name", "swarm-wrapper",
8
+ "--distpath", "~/bin",
9
+ "--workpath", "build",
10
+ "--specpath", "."
11
+ ])
12
+
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import subprocess
4
+ import sys
5
+ from os import path, listdir, makedirs
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser(description="Swarm REST Launcher")
9
+ parser.add_argument("--blueprint", required=True, help="Comma-separated blueprint file paths or names for configuration purposes")
10
+ parser.add_argument("--port", type=int, default=8000, help="Port to run the REST server")
11
+ parser.add_argument("--config", default="~/.swarm/swarm_config.json", help="Configuration file path")
12
+ parser.add_argument("--daemon", action="store_true", help="Run in daemon mode and print process id")
13
+ args = parser.parse_args()
14
+
15
+ # Split blueprints by comma and strip whitespace
16
+ bp_list = [bp.strip() for bp in args.blueprint.split(",") if bp.strip()]
17
+ blueprint_paths = []
18
+ for bp_arg in bp_list:
19
+ resolved = None
20
+ if path.exists(bp_arg):
21
+ if path.isdir(bp_arg):
22
+ resolved = bp_arg
23
+ print(f"Using blueprint directory: {resolved}")
24
+ else:
25
+ resolved = bp_arg
26
+ print(f"Using blueprint file: {resolved}")
27
+ else:
28
+ managed_path = path.expanduser("~/.swarm/blueprints/" + bp_arg)
29
+ if path.isdir(managed_path):
30
+ matches = [f for f in listdir(managed_path) if f.startswith("blueprint_") and f.endswith(".py")]
31
+ if not matches:
32
+ print("Error: No blueprint file found in managed directory:", managed_path)
33
+ sys.exit(1)
34
+ resolved = path.join(managed_path, matches[0])
35
+ print(f"Using managed blueprint: {resolved}")
36
+ else:
37
+ print("Warning: Blueprint not found:", bp_arg, "- skipping.")
38
+ continue
39
+ if resolved:
40
+ blueprint_paths.append(resolved)
41
+
42
+ if not blueprint_paths:
43
+ print("Error: No valid blueprints found.")
44
+ sys.exit(1)
45
+ print("Blueprints to be configured:")
46
+ for bp in blueprint_paths:
47
+ print(" -", bp)
48
+
49
+ config_path = path.expanduser(args.config)
50
+ if not path.exists(config_path):
51
+ makedirs(path.dirname(config_path), exist_ok=True)
52
+ with open(config_path, 'w') as f:
53
+ f.write("{}")
54
+ print("Default config file created at:", config_path)
55
+
56
+ print("Launching Django server on port 0.0.0.0:{}".format(args.port))
57
+ try:
58
+ if args.daemon:
59
+ proc = subprocess.Popen(["python", "manage.py", "runserver", f"0.0.0.0:{args.port}"])
60
+ print("Running in daemon mode. Process ID:", proc.pid)
61
+ else:
62
+ subprocess.run(["python", "manage.py", "runserver", f"0.0.0.0:{args.port}"], check=True)
63
+ except subprocess.CalledProcessError as e:
64
+ print("Error launching Django server:", e)
65
+ sys.exit(1)
66
+
67
+ if __name__ == "__main__":
68
+ main()
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import importlib.util
4
+ import os
5
+ import sys
6
+ import subprocess
7
+ import shutil
8
+ import json
9
+ import PyInstaller.__main__
10
+
11
+ def resolve_env_vars(data):
12
+ if isinstance(data, dict):
13
+ return {k: resolve_env_vars(v) for k, v in data.items()}
14
+ elif isinstance(data, list):
15
+ return [resolve_env_vars(item) for item in data]
16
+ elif isinstance(data, str):
17
+ return os.path.expandvars(data)
18
+ else:
19
+ return data
20
+
21
+ MANAGED_DIR = os.path.expanduser("~/.swarm/blueprints")
22
+ BIN_DIR = os.path.expanduser("~/.swarm/bin")
23
+
24
+ def ensure_managed_dir():
25
+ if not os.path.exists(MANAGED_DIR):
26
+ os.makedirs(MANAGED_DIR, exist_ok=True)
27
+ if not os.path.exists(BIN_DIR):
28
+ os.makedirs(BIN_DIR, exist_ok=True)
29
+
30
+ def add_blueprint(source_path, blueprint_name=None):
31
+ source_path = os.path.normpath(source_path)
32
+ if not os.path.exists(source_path):
33
+ print("Error: source file/directory does not exist:", source_path)
34
+ sys.exit(1)
35
+ if os.path.isdir(source_path):
36
+ if not blueprint_name:
37
+ blueprint_name = os.path.basename(os.path.normpath(source_path))
38
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
39
+ if os.path.exists(target_dir):
40
+ shutil.rmtree(target_dir)
41
+ os.makedirs(target_dir, exist_ok=True)
42
+ for root, dirs, files in os.walk(source_path):
43
+ rel_path = os.path.relpath(root, source_path)
44
+ dest_root = os.path.join(target_dir, rel_path) if rel_path != '.' else target_dir
45
+ os.makedirs(dest_root, exist_ok=True)
46
+ for file in files:
47
+ shutil.copy2(os.path.join(root, file), os.path.join(dest_root, file))
48
+ print(f"Blueprint '{blueprint_name}' added successfully to {target_dir}.")
49
+ else:
50
+ blueprint_file = source_path
51
+ if not blueprint_name:
52
+ base = os.path.basename(blueprint_file)
53
+ if base.startswith("blueprint_") and base.endswith(".py"):
54
+ blueprint_name = base[len("blueprint_"):-3]
55
+ else:
56
+ blueprint_name = os.path.splitext(base)[0]
57
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
58
+ os.makedirs(target_dir, exist_ok=True)
59
+ target_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
60
+ shutil.copy2(blueprint_file, target_file)
61
+ print(f"Blueprint '{blueprint_name}' added successfully to {target_dir}.")
62
+
63
+ def list_blueprints():
64
+ ensure_managed_dir()
65
+ entries = os.listdir(MANAGED_DIR)
66
+ blueprints = [d for d in entries if os.path.isdir(os.path.join(MANAGED_DIR, d))]
67
+ if blueprints:
68
+ print("Registered blueprints:")
69
+ for bp in blueprints:
70
+ print(" -", bp)
71
+ else:
72
+ print("No blueprints registered.")
73
+
74
+ def delete_blueprint(blueprint_name):
75
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
76
+ if os.path.exists(target_dir) and os.path.isdir(target_dir):
77
+ shutil.rmtree(target_dir)
78
+ print(f"Blueprint '{blueprint_name}' deleted successfully.")
79
+ else:
80
+ print(f"Error: Blueprint '{blueprint_name}' does not exist.")
81
+ sys.exit(1)
82
+
83
+ def run_blueprint(blueprint_name):
84
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
85
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
86
+ if not os.path.exists(blueprint_file):
87
+ print(f"Error: Blueprint file not found for '{blueprint_name}'. Install it using 'swarm-cli add <path>'.")
88
+ sys.exit(1)
89
+ spec = importlib.util.spec_from_file_location("blueprint_module", blueprint_file)
90
+ if spec is None or spec.loader is None:
91
+ print("Error: Failed to load blueprint module from:", blueprint_file)
92
+ sys.exit(1)
93
+ blueprint = importlib.util.module_from_spec(spec)
94
+ loader = spec.loader
95
+ src_path = os.path.join(os.getcwd(), "src")
96
+ if src_path not in sys.path:
97
+ sys.path.insert(0, src_path)
98
+ loader.exec_module(blueprint)
99
+ if hasattr(blueprint, "main"):
100
+ blueprint.main()
101
+ else:
102
+ print("Error: The blueprint does not have a main() function.")
103
+ sys.exit(1)
104
+
105
+ def install_blueprint(blueprint_name):
106
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
107
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
108
+ if not os.path.exists(blueprint_file):
109
+ print(f"Error: Blueprint '{blueprint_name}' is not registered. Add it using 'swarm-cli add <path>'.")
110
+ sys.exit(1)
111
+ cli_name = blueprint_name # Use blueprint_name as default cli_name for simplicity
112
+ try:
113
+ PyInstaller.__main__.run([
114
+ blueprint_file,
115
+ "--onefile",
116
+ "--name", cli_name,
117
+ "--distpath", BIN_DIR,
118
+ "--workpath", os.path.join(target_dir, "build"),
119
+ "--specpath", target_dir
120
+ ])
121
+ except KeyboardInterrupt:
122
+ print("Installation aborted by user request.")
123
+ sys.exit(1)
124
+ print(f"Blueprint '{blueprint_name}' installed as CLI utility '{cli_name}' at: {os.path.join(BIN_DIR, cli_name)}")
125
+
126
+ def uninstall_blueprint(blueprint_name, blueprint_only=False, wrapper_only=False):
127
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
128
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
129
+ cli_name = blueprint_name # Default to blueprint_name for uninstall
130
+ cli_path = os.path.join(BIN_DIR, cli_name)
131
+ removed = False
132
+
133
+ if not blueprint_only and not wrapper_only: # Remove both by default
134
+ if os.path.exists(target_dir):
135
+ shutil.rmtree(target_dir)
136
+ print(f"Blueprint '{blueprint_name}' removed from {MANAGED_DIR}.")
137
+ removed = True
138
+ if os.path.exists(cli_path):
139
+ os.remove(cli_path)
140
+ print(f"Wrapper '{cli_name}' removed from {BIN_DIR}.")
141
+ removed = True
142
+ elif blueprint_only:
143
+ if os.path.exists(target_dir):
144
+ shutil.rmtree(target_dir)
145
+ print(f"Blueprint '{blueprint_name}' removed from {MANAGED_DIR}.")
146
+ removed = True
147
+ elif wrapper_only:
148
+ if os.path.exists(cli_path):
149
+ os.remove(cli_path)
150
+ print(f"Wrapper '{cli_name}' removed from {BIN_DIR}.")
151
+ removed = True
152
+
153
+ if not removed:
154
+ print(f"Error: Nothing to uninstall for '{blueprint_name}' with specified options.")
155
+ sys.exit(1)
156
+
157
+ def main():
158
+ os.environ.pop("SWARM_BLUEPRINTS", None)
159
+ parser = argparse.ArgumentParser(
160
+ description="Swarm CLI Launcher\n\nSubcommands:\n"
161
+ " add : Add a blueprint to the managed directory.\n"
162
+ " list : List registered blueprints.\n"
163
+ " delete : Delete a registered blueprint.\n"
164
+ " run : Run a blueprint by name.\n"
165
+ " install : Install a blueprint as a CLI utility with PyInstaller.\n"
166
+ " uninstall : Uninstall a blueprint and/or its CLI wrapper.\n"
167
+ " migrate : Apply Django database migrations.\n"
168
+ " config : Manage swarm configuration (LLM and MCP servers).",
169
+ formatter_class=argparse.RawTextHelpFormatter)
170
+ subparsers = parser.add_subparsers(dest="command", required=True, help="Available subcommands")
171
+
172
+ parser_add = subparsers.add_parser("add", help="Add a blueprint from a file or directory.")
173
+ parser_add.add_argument("source", help="Source blueprint file or directory.")
174
+ parser_add.add_argument("--name", help="Optional blueprint name. If not provided, inferred from filename.")
175
+
176
+ parser_list = subparsers.add_parser("list", help="List registered blueprints.")
177
+
178
+ parser_delete = subparsers.add_parser("delete", help="Delete a registered blueprint by name.")
179
+ parser_delete.add_argument("name", help="Blueprint name to delete.")
180
+
181
+ parser_run = subparsers.add_parser("run", help="Run a blueprint by name.")
182
+ parser_run.add_argument("name", help="Blueprint name to run.")
183
+ parser_run.add_argument("--config", default="~/.swarm/swarm_config.json", help="Path to configuration file.")
184
+
185
+ parser_install = subparsers.add_parser("install", help="Install a blueprint as a CLI utility with PyInstaller.")
186
+ parser_install.add_argument("name", help="Blueprint name to install as a CLI utility.")
187
+
188
+ parser_uninstall = subparsers.add_parser("uninstall", help="Uninstall a blueprint and/or its CLI wrapper.")
189
+ parser_uninstall.add_argument("name", help="Blueprint name to uninstall.")
190
+ parser_uninstall.add_argument("--blueprint-only", action="store_true", help="Remove only the blueprint directory.")
191
+ parser_uninstall.add_argument("--wrapper-only", action="store_true", help="Remove only the CLI wrapper.")
192
+
193
+ parser_migrate = subparsers.add_parser("migrate", help="Apply Django database migrations.")
194
+
195
+ parser_config = subparsers.add_parser("config", help="Manage swarm configuration (LLM and MCP servers).")
196
+ parser_config.add_argument("action", choices=["add", "list", "remove"], help="Action to perform on configuration")
197
+ parser_config.add_argument("--section", required=True, choices=["llm", "mcpServers"], help="Configuration section to manage")
198
+ parser_config.add_argument("--name", help="Name of the configuration entry (required for add and remove)")
199
+ parser_config.add_argument("--json", help="JSON string for configuration entry (required for add)")
200
+ parser_config.add_argument("--config", default="~/.swarm/swarm_config.json", help="Path to configuration file")
201
+
202
+ args = parser.parse_args()
203
+ ensure_managed_dir()
204
+
205
+ if args.command == "add":
206
+ add_blueprint(args.source, args.name)
207
+ elif args.command == "list":
208
+ list_blueprints()
209
+ elif args.command == "delete":
210
+ delete_blueprint(args.name)
211
+ elif args.command == "run":
212
+ config_path = os.path.expanduser(args.config)
213
+ if not os.path.exists(config_path):
214
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
215
+ default_config = {"llm": {}, "mcpServers": {}}
216
+ with open(config_path, 'w') as f:
217
+ json.dump(default_config, f, indent=4)
218
+ print("Default config file created at:", config_path)
219
+ run_blueprint(args.name)
220
+ elif args.command == "install":
221
+ install_blueprint(args.name)
222
+ elif args.command == "uninstall":
223
+ uninstall_blueprint(args.name, args.blueprint_only, args.wrapper_only)
224
+ elif args.command == "migrate":
225
+ try:
226
+ subprocess.run(["python", "manage.py", "migrate"], check=True)
227
+ print("Migrations applied successfully.")
228
+ except subprocess.CalledProcessError as e:
229
+ print("Error applying migrations:", e)
230
+ sys.exit(1)
231
+ elif args.command == "config":
232
+ config_path = os.path.expanduser(args.config)
233
+ if not os.path.exists(config_path):
234
+ default_conf = {"llm": {}, "mcpServers": {}}
235
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
236
+ with open(config_path, "w") as f:
237
+ json.dump(default_conf, f, indent=4)
238
+ print("Default config file created at:", config_path)
239
+ config = default_conf
240
+ else:
241
+ try:
242
+ with open(config_path, "r") as f:
243
+ config = json.load(f)
244
+ except json.JSONDecodeError:
245
+ print("Error: Invalid configuration file.")
246
+ sys.exit(1)
247
+ section = args.section
248
+ if args.action == "list":
249
+ entries = config.get(section, {})
250
+ if entries:
251
+ print(f"Entries in {section}:")
252
+ for key, value in entries.items():
253
+ print(f" - {key}: {json.dumps(value, indent=4)}")
254
+ else:
255
+ print(f"No entries found in {section}.")
256
+ elif args.action == "add":
257
+ if args.section == "mcpServers" and not args.name:
258
+ if not args.json:
259
+ print("Error: --json is required for adding an mcpServers block when --name is omitted.")
260
+ sys.exit(1)
261
+ try:
262
+ update_data = json.loads(args.json)
263
+ except json.JSONDecodeError:
264
+ print("Error: --json must be a valid JSON string.")
265
+ sys.exit(1)
266
+ if "mcpServers" not in update_data:
267
+ print("Error: JSON block must contain 'mcpServers' key for merging.")
268
+ sys.exit(1)
269
+ config.setdefault("mcpServers", {})
270
+ config["mcpServers"].update(update_data["mcpServers"])
271
+ with open(config_path, "w") as f:
272
+ json.dump(config, f, indent=4)
273
+ print("MCP servers updated in configuration.")
274
+ else:
275
+ if not args.name or not args.json:
276
+ print("Error: --name and --json are required for adding an entry.")
277
+ sys.exit(1)
278
+ try:
279
+ entry_data = json.loads(args.json)
280
+ except json.JSONDecodeError:
281
+ print("Error: --json must be a valid JSON string.")
282
+ sys.exit(1)
283
+ config.setdefault(section, {})[args.name] = entry_data
284
+ with open(config_path, "w") as f:
285
+ json.dump(config, f, indent=4)
286
+ print(f"Entry '{args.name}' added to {section} in configuration.")
287
+ elif args.action == "remove":
288
+ if not args.name:
289
+ print("Error: --name is required for removing an entry.")
290
+ sys.exit(1)
291
+ if args.name in config.get(section, {}):
292
+ del config[section][args.name]
293
+ with open(config_path, "w") as f:
294
+ json.dump(config, f, indent=4)
295
+ print(f"Entry '{args.name}' removed from {section} in configuration.")
296
+ else:
297
+ print(f"Error: Entry '{args.name}' not found in {section}.")
298
+ sys.exit(1)
299
+ else:
300
+ parser.print_help()
301
+ sys.exit(1)
302
+
303
+ if __name__ == "__main__":
304
+ main()
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import subprocess
5
+
6
+ MANAGED_DIR = os.path.expanduser("~/.swarm/blueprints")
7
+ BIN_DIR = os.path.expanduser("~/.swarm/bin")
8
+
9
+ def main():
10
+ if len(sys.argv) < 2:
11
+ print("Usage: swarm-wrapper <cli_name> [args...]")
12
+ sys.exit(1)
13
+
14
+ cli_name = sys.argv[1]
15
+ blueprint_name = cli_name # Default assumption; could map via config if needed
16
+ blueprint_dir = os.path.join(MANAGED_DIR, blueprint_name)
17
+ blueprint_file = os.path.join(blueprint_dir, f"blueprint_{blueprint_name}.py")
18
+ cli_path = os.path.join(BIN_DIR, cli_name)
19
+
20
+ if os.path.exists(cli_path):
21
+ # Run the installed CLI
22
+ subprocess.run([cli_path] + sys.argv[2:], check=False)
23
+ else:
24
+ print(f"Error: Blueprint '{blueprint_name}' not installed for CLI '{cli_name}'.")
25
+ print(f"Please install it using: swarm-cli add <path_to_{blueprint_name}> && swarm-cli install {blueprint_name}")
26
+ sys.exit(1)
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1 @@
1
+ # This is the __init__.py for the 'mcp' package.
@@ -0,0 +1,36 @@
1
+ # cache_utils.py
2
+
3
+ from typing import Any
4
+
5
+ class DummyCache:
6
+ """A dummy cache that performs no operations."""
7
+ def get(self, key: str, default: Any = None) -> Any:
8
+ return default
9
+
10
+ def set(self, key: str, value: Any, timeout: int = None) -> None:
11
+ pass
12
+
13
+ def get_cache():
14
+ """
15
+ Attempts to retrieve Django's cache. If Django isn't available or configured,
16
+ returns a DummyCache instance.
17
+ """
18
+ try:
19
+ # Intentionally commented out Django imports to avoid mandatory dependency for now
20
+ # import django
21
+ # from django.conf import settings
22
+ # from django.core.cache import cache as django_cache
23
+ # from django.core.exceptions import ImproperlyConfigured
24
+
25
+ # if not settings.configured:
26
+ # # Django settings are not configured; return DummyCache
27
+ # return DummyCache()
28
+
29
+ # return django_cache
30
+ # For now, always return DummyCache until Django integration is confirmed/required
31
+ return DummyCache()
32
+
33
+
34
+ except (ImportError): # Removed ImproperlyConfigured as Django is not imported
35
+ # Django is not installed or not properly configured; use DummyCache
36
+ return DummyCache()