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.
- open_swarm-0.1.1743070217.dist-info/METADATA +258 -0
- open_swarm-0.1.1743070217.dist-info/RECORD +89 -0
- open_swarm-0.1.1743070217.dist-info/WHEEL +5 -0
- open_swarm-0.1.1743070217.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1743070217.dist-info/licenses/LICENSE +21 -0
- open_swarm-0.1.1743070217.dist-info/top_level.txt +1 -0
- swarm/__init__.py +3 -0
- swarm/agent/__init__.py +7 -0
- swarm/agent/agent.py +49 -0
- swarm/apps.py +53 -0
- swarm/auth.py +56 -0
- swarm/consumers.py +141 -0
- swarm/core.py +326 -0
- swarm/extensions/__init__.py +1 -0
- swarm/extensions/blueprint/__init__.py +36 -0
- swarm/extensions/blueprint/agent_utils.py +45 -0
- swarm/extensions/blueprint/blueprint_base.py +562 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/blueprint_utils.py +17 -0
- swarm/extensions/blueprint/common_utils.py +12 -0
- swarm/extensions/blueprint/django_utils.py +203 -0
- swarm/extensions/blueprint/interactive_mode.py +102 -0
- swarm/extensions/blueprint/modes/rest_mode.py +37 -0
- swarm/extensions/blueprint/output_utils.py +95 -0
- swarm/extensions/blueprint/spinner.py +91 -0
- swarm/extensions/cli/__init__.py +0 -0
- swarm/extensions/cli/blueprint_runner.py +251 -0
- swarm/extensions/cli/cli_args.py +88 -0
- swarm/extensions/cli/commands/__init__.py +0 -0
- swarm/extensions/cli/commands/blueprint_management.py +31 -0
- swarm/extensions/cli/commands/config_management.py +15 -0
- swarm/extensions/cli/commands/edit_config.py +77 -0
- swarm/extensions/cli/commands/list_blueprints.py +22 -0
- swarm/extensions/cli/commands/validate_env.py +57 -0
- swarm/extensions/cli/commands/validate_envvars.py +39 -0
- swarm/extensions/cli/interactive_shell.py +41 -0
- swarm/extensions/cli/main.py +36 -0
- swarm/extensions/cli/selection.py +43 -0
- swarm/extensions/cli/utils/discover_commands.py +32 -0
- swarm/extensions/cli/utils/env_setup.py +15 -0
- swarm/extensions/cli/utils.py +105 -0
- swarm/extensions/config/__init__.py +6 -0
- swarm/extensions/config/config_loader.py +208 -0
- swarm/extensions/config/config_manager.py +258 -0
- swarm/extensions/config/server_config.py +49 -0
- swarm/extensions/config/setup_wizard.py +103 -0
- swarm/extensions/config/utils/__init__.py +0 -0
- swarm/extensions/config/utils/logger.py +36 -0
- swarm/extensions/launchers/__init__.py +1 -0
- swarm/extensions/launchers/build_launchers.py +14 -0
- swarm/extensions/launchers/build_swarm_wrapper.py +12 -0
- swarm/extensions/launchers/swarm_api.py +68 -0
- swarm/extensions/launchers/swarm_cli.py +304 -0
- swarm/extensions/launchers/swarm_wrapper.py +29 -0
- swarm/extensions/mcp/__init__.py +1 -0
- swarm/extensions/mcp/cache_utils.py +36 -0
- swarm/extensions/mcp/mcp_client.py +341 -0
- swarm/extensions/mcp/mcp_constants.py +7 -0
- swarm/extensions/mcp/mcp_tool_provider.py +110 -0
- swarm/llm/chat_completion.py +195 -0
- swarm/messages.py +132 -0
- swarm/migrations/0010_initial_chat_models.py +51 -0
- swarm/migrations/__init__.py +0 -0
- swarm/models.py +45 -0
- swarm/repl/__init__.py +1 -0
- swarm/repl/repl.py +87 -0
- swarm/serializers.py +12 -0
- swarm/settings.py +189 -0
- swarm/tool_executor.py +239 -0
- swarm/types.py +126 -0
- swarm/urls.py +89 -0
- swarm/util.py +124 -0
- swarm/utils/color_utils.py +40 -0
- swarm/utils/context_utils.py +272 -0
- swarm/utils/general_utils.py +162 -0
- swarm/utils/logger.py +61 -0
- swarm/utils/logger_setup.py +25 -0
- swarm/utils/message_sequence.py +173 -0
- swarm/utils/message_utils.py +95 -0
- swarm/utils/redact.py +68 -0
- swarm/views/__init__.py +41 -0
- swarm/views/api_views.py +46 -0
- swarm/views/chat_views.py +76 -0
- swarm/views/core_views.py +118 -0
- swarm/views/message_views.py +40 -0
- swarm/views/model_views.py +135 -0
- swarm/views/utils.py +457 -0
- swarm/views/web_views.py +149 -0
- 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,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()
|