open-swarm 0.1.1744943015__py3-none-any.whl → 0.1.1744947037__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.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/RECORD +25 -5
- swarm/core/agent_utils.py +21 -0
- swarm/core/blueprint_base.py +395 -0
- swarm/core/blueprint_discovery.py +128 -0
- swarm/core/blueprint_runner.py +59 -0
- swarm/core/blueprint_utils.py +17 -0
- swarm/core/build_launchers.py +14 -0
- swarm/core/build_swarm_wrapper.py +12 -0
- swarm/core/common_utils.py +12 -0
- swarm/core/config_loader.py +122 -0
- swarm/core/config_manager.py +274 -0
- swarm/core/output_utils.py +173 -0
- swarm/core/server_config.py +81 -0
- swarm/core/setup_wizard.py +103 -0
- swarm/core/slash_commands.py +17 -0
- swarm/core/spinner.py +100 -0
- swarm/core/swarm_api.py +68 -0
- swarm/core/swarm_cli.py +216 -0
- swarm/core/swarm_wrapper.py +29 -0
- swarm/core/utils/__init__.py +0 -0
- swarm/core/utils/logger.py +36 -0
- {open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,81 @@
|
|
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
|
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,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
|
@@ -0,0 +1,17 @@
|
|
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()
|
swarm/core/spinner.py
ADDED
@@ -0,0 +1,100 @@
|
|
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.")
|
swarm/core/swarm_api.py
ADDED
@@ -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()
|
swarm/core/swarm_cli.py
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
import os
|
2
|
+
import typer
|
3
|
+
import platformdirs
|
4
|
+
import subprocess
|
5
|
+
import sys
|
6
|
+
from pathlib import Path
|
7
|
+
import importlib.resources as pkg_resources
|
8
|
+
import swarm # Import the main package to access its resources
|
9
|
+
|
10
|
+
# --- Configuration ---
|
11
|
+
APP_NAME = "swarm"
|
12
|
+
APP_AUTHOR = "swarm-authors" # Replace if needed
|
13
|
+
|
14
|
+
# Use platformdirs for user-specific data, config locations
|
15
|
+
USER_DATA_DIR = Path(os.getenv("SWARM_USER_DATA_DIR", platformdirs.user_data_dir(APP_NAME, APP_AUTHOR)))
|
16
|
+
USER_CONFIG_DIR = Path(os.getenv("SWARM_USER_CONFIG_DIR", platformdirs.user_config_dir(APP_NAME, APP_AUTHOR)))
|
17
|
+
|
18
|
+
# *** CORRECTED: Define user bin dir as a subdir of user data dir ***
|
19
|
+
USER_BIN_DIR = Path(os.getenv("SWARM_USER_BIN_DIR", USER_DATA_DIR / "bin"))
|
20
|
+
|
21
|
+
# Derived paths
|
22
|
+
BLUEPRINTS_DIR = USER_DATA_DIR / "blueprints"
|
23
|
+
INSTALLED_BIN_DIR = USER_BIN_DIR # Keep using this variable name for clarity
|
24
|
+
|
25
|
+
# Ensure directories exist
|
26
|
+
BLUEPRINTS_DIR.mkdir(parents=True, exist_ok=True)
|
27
|
+
INSTALLED_BIN_DIR.mkdir(parents=True, exist_ok=True) # Ensure the user bin dir is created
|
28
|
+
USER_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
29
|
+
|
30
|
+
|
31
|
+
# --- Typer App ---
|
32
|
+
app = typer.Typer(
|
33
|
+
help="Swarm CLI tool for managing blueprints.",
|
34
|
+
add_completion=True # Enable shell completion commands
|
35
|
+
)
|
36
|
+
|
37
|
+
# --- Helper Functions ---
|
38
|
+
def find_entry_point(blueprint_dir: Path) -> str | None:
|
39
|
+
"""Placeholder: Finds the main Python script in a blueprint directory."""
|
40
|
+
# Improve this logic: Look for a specific file, check pyproject.toml, etc.
|
41
|
+
for item in blueprint_dir.glob("*.py"):
|
42
|
+
if item.is_file() and not item.name.startswith("_"):
|
43
|
+
return item.name
|
44
|
+
return None
|
45
|
+
|
46
|
+
# --- CLI Commands ---
|
47
|
+
|
48
|
+
@app.command()
|
49
|
+
def install(
|
50
|
+
blueprint_name: str = typer.Argument(..., help="Name of the blueprint directory to install."),
|
51
|
+
# Add options for specifying source dir if needed later
|
52
|
+
):
|
53
|
+
"""
|
54
|
+
Install a blueprint by creating a standalone executable using PyInstaller.
|
55
|
+
"""
|
56
|
+
# Decide where to look for the source blueprint: User dir first, then bundled?
|
57
|
+
# For now, let's assume it must exist in the user dir for installation
|
58
|
+
# TODO: Enhance this to allow installing bundled blueprints directly?
|
59
|
+
source_dir = BLUEPRINTS_DIR / blueprint_name
|
60
|
+
if not source_dir.is_dir():
|
61
|
+
# Could also check bundled blueprints here if desired
|
62
|
+
typer.echo(f"Error: Blueprint source directory not found in user directory: {source_dir}", err=True)
|
63
|
+
typer.echo("Currently, only blueprints placed in the user directory can be installed.", err=True)
|
64
|
+
raise typer.Exit(code=1)
|
65
|
+
|
66
|
+
entry_point = find_entry_point(source_dir)
|
67
|
+
if not entry_point:
|
68
|
+
typer.echo(f"Error: Could not find entry point script in {source_dir}", err=True)
|
69
|
+
raise typer.Exit(code=1)
|
70
|
+
|
71
|
+
entry_point_path = source_dir / entry_point
|
72
|
+
output_bin = INSTALLED_BIN_DIR / blueprint_name
|
73
|
+
dist_path = USER_DATA_DIR / "dist" # PyInstaller dist path within user data
|
74
|
+
build_path = USER_DATA_DIR / "build" # PyInstaller build path within user data
|
75
|
+
|
76
|
+
typer.echo(f"Installing blueprint '{blueprint_name}'...")
|
77
|
+
typer.echo(f" Source: {source_dir}")
|
78
|
+
typer.echo(f" Entry Point: {entry_point}")
|
79
|
+
typer.echo(f" Output Executable: {output_bin}")
|
80
|
+
|
81
|
+
pyinstaller_cmd = [
|
82
|
+
"pyinstaller",
|
83
|
+
"--onefile", # Create a single executable file
|
84
|
+
"--name", str(output_bin.name), # Name of the output executable
|
85
|
+
"--distpath", str(INSTALLED_BIN_DIR), # Output directory for the executable
|
86
|
+
"--workpath", str(build_path), # Directory for temporary build files
|
87
|
+
"--specpath", str(USER_DATA_DIR), # Directory for the .spec file
|
88
|
+
str(entry_point_path), # The main script to bundle
|
89
|
+
]
|
90
|
+
|
91
|
+
typer.echo(f"Running PyInstaller: {' '.join(map(str, pyinstaller_cmd))}") # Use map(str,...) for Path objects
|
92
|
+
|
93
|
+
try:
|
94
|
+
# Use subprocess.run for better control and error handling
|
95
|
+
result = subprocess.run(pyinstaller_cmd, check=True, capture_output=True, text=True)
|
96
|
+
typer.echo("PyInstaller output:")
|
97
|
+
typer.echo(result.stdout)
|
98
|
+
typer.echo(f"Successfully installed '{blueprint_name}' to {output_bin}")
|
99
|
+
except FileNotFoundError:
|
100
|
+
typer.echo("Error: PyInstaller command not found. Is PyInstaller installed?", err=True)
|
101
|
+
raise typer.Exit(code=1)
|
102
|
+
except subprocess.CalledProcessError as e:
|
103
|
+
typer.echo(f"Error during PyInstaller execution (Return Code: {e.returncode}):", err=True)
|
104
|
+
typer.echo(e.stderr, err=True)
|
105
|
+
typer.echo("Check the output above for details.", err=True)
|
106
|
+
raise typer.Exit(code=1)
|
107
|
+
except Exception as e:
|
108
|
+
typer.echo(f"An unexpected error occurred: {e}", err=True)
|
109
|
+
raise typer.Exit(code=1)
|
110
|
+
|
111
|
+
|
112
|
+
@app.command()
|
113
|
+
def launch(blueprint_name: str = typer.Argument(..., help="Name of the installed blueprint executable to launch.")):
|
114
|
+
"""
|
115
|
+
Launch a previously installed blueprint executable.
|
116
|
+
"""
|
117
|
+
executable_path = INSTALLED_BIN_DIR / blueprint_name
|
118
|
+
if not executable_path.is_file() or not os.access(executable_path, os.X_OK):
|
119
|
+
typer.echo(f"Error: Blueprint executable not found or not executable: {executable_path}", err=True)
|
120
|
+
raise typer.Exit(code=1)
|
121
|
+
|
122
|
+
typer.echo(f"Launching '{blueprint_name}' from {executable_path}...")
|
123
|
+
try:
|
124
|
+
# Execute the blueprint directly
|
125
|
+
# Using subprocess.run is generally safer and more flexible than os.execv
|
126
|
+
result = subprocess.run([str(executable_path)], capture_output=True, text=True, check=False) # check=False to handle non-zero exits gracefully
|
127
|
+
typer.echo(f"--- {blueprint_name} Output ---")
|
128
|
+
typer.echo(result.stdout)
|
129
|
+
if result.stderr:
|
130
|
+
typer.echo("--- Errors/Warnings ---", err=True)
|
131
|
+
typer.echo(result.stderr, err=True)
|
132
|
+
typer.echo(f"--- '{blueprint_name}' finished (Return Code: {result.returncode}) ---")
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
typer.echo(f"Error launching blueprint: {e}", err=True)
|
136
|
+
raise typer.Exit(code=1)
|
137
|
+
|
138
|
+
|
139
|
+
@app.command(name="list")
|
140
|
+
def list_blueprints(
|
141
|
+
installed: bool = typer.Option(False, "--installed", "-i", help="List only installed blueprint executables."),
|
142
|
+
available: bool = typer.Option(False, "--available", "-a", help="List only available blueprints (source dirs).")
|
143
|
+
):
|
144
|
+
"""
|
145
|
+
Lists available blueprints (bundled and user-provided) and/or installed executables.
|
146
|
+
"""
|
147
|
+
list_installed = not available or installed
|
148
|
+
list_available = not installed or available
|
149
|
+
|
150
|
+
# --- List Installed Blueprints ---
|
151
|
+
if list_installed:
|
152
|
+
typer.echo(f"--- Installed Blueprints (in {INSTALLED_BIN_DIR}) ---")
|
153
|
+
found_installed = False
|
154
|
+
if INSTALLED_BIN_DIR.exists():
|
155
|
+
try:
|
156
|
+
for item in INSTALLED_BIN_DIR.iterdir():
|
157
|
+
# Basic check: is it a file and executable? Refine as needed.
|
158
|
+
if item.is_file() and os.access(item, os.X_OK):
|
159
|
+
typer.echo(f"- {item.name}")
|
160
|
+
found_installed = True
|
161
|
+
except OSError as e:
|
162
|
+
typer.echo(f"(Warning: Could not read installed directory: {e})", err=True)
|
163
|
+
if not found_installed:
|
164
|
+
typer.echo("(No installed blueprint executables found)")
|
165
|
+
typer.echo("") # Add spacing
|
166
|
+
|
167
|
+
# --- List Available Blueprints (Bundled and User) ---
|
168
|
+
if list_available:
|
169
|
+
# --- Bundled ---
|
170
|
+
typer.echo("--- Bundled Blueprints (installed with package) ---")
|
171
|
+
bundled_found = False
|
172
|
+
try:
|
173
|
+
# Use importlib.resources to access the 'blueprints' directory within the installed 'swarm' package
|
174
|
+
bundled_blueprints_path = pkg_resources.files(swarm) / 'blueprints'
|
175
|
+
|
176
|
+
if bundled_blueprints_path.is_dir(): # Check if the directory exists in the package
|
177
|
+
for item in bundled_blueprints_path.iterdir():
|
178
|
+
# Check if it's a directory containing an entry point (adapt check as needed)
|
179
|
+
if item.is_dir() and not item.name.startswith("__"): # Skip __pycache__ etc.
|
180
|
+
entry_point = find_entry_point(item) # Use helper, might need refinement
|
181
|
+
if entry_point:
|
182
|
+
typer.echo(f"- {item.name} (entry: {entry_point})")
|
183
|
+
bundled_found = True
|
184
|
+
except ModuleNotFoundError:
|
185
|
+
typer.echo("(Could not find bundled blueprints - package structure issue?)", err=True)
|
186
|
+
except FileNotFoundError: # Can happen if package data wasn't included correctly
|
187
|
+
typer.echo("(Could not find bundled blueprints path - package data missing?)", err=True)
|
188
|
+
except Exception as e:
|
189
|
+
typer.echo(f"(Error accessing bundled blueprints: {e})", err=True)
|
190
|
+
|
191
|
+
if not bundled_found:
|
192
|
+
typer.echo("(No bundled blueprints found or accessible)")
|
193
|
+
typer.echo("") # Add spacing
|
194
|
+
|
195
|
+
# --- User ---
|
196
|
+
typer.echo(f"--- User Blueprints (in {BLUEPRINTS_DIR}) ---")
|
197
|
+
user_found = False
|
198
|
+
if BLUEPRINTS_DIR.exists() and BLUEPRINTS_DIR.is_dir():
|
199
|
+
try:
|
200
|
+
for item in BLUEPRINTS_DIR.iterdir():
|
201
|
+
if item.is_dir():
|
202
|
+
entry_point = find_entry_point(item) # Use helper
|
203
|
+
if entry_point:
|
204
|
+
typer.echo(f"- {item.name} (entry: {entry_point})")
|
205
|
+
user_found = True
|
206
|
+
except OSError as e:
|
207
|
+
typer.echo(f"(Warning: Could not read user blueprints directory: {e})", err=True)
|
208
|
+
|
209
|
+
if not user_found:
|
210
|
+
typer.echo("(No user blueprints found)")
|
211
|
+
typer.echo("") # Add spacing
|
212
|
+
|
213
|
+
|
214
|
+
# --- Main Execution Guard ---
|
215
|
+
if __name__ == "__main__":
|
216
|
+
app()
|
@@ -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()
|
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
|
File without changes
|
{open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/entry_points.txt
RENAMED
File without changes
|
{open_swarm-0.1.1744943015.dist-info → open_swarm-0.1.1744947037.dist-info}/licenses/LICENSE
RENAMED
File without changes
|