open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__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 (217) hide show
  1. open_swarm-0.1.1743364176.dist-info/METADATA +286 -0
  2. open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743364176.dist-info/entry_points.txt +2 -0
  5. swarm/__init__.py +0 -2
  6. swarm/auth.py +53 -49
  7. swarm/blueprints/README.md +67 -0
  8. swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
  9. swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
  10. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
  11. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
  12. swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
  13. swarm/blueprints/divine_code/__init__.py +0 -0
  14. swarm/blueprints/divine_code/apps.py +11 -0
  15. swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
  16. swarm/blueprints/django_chat/apps.py +6 -0
  17. swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
  18. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
  19. swarm/blueprints/django_chat/urls.py +8 -0
  20. swarm/blueprints/django_chat/views.py +32 -0
  21. swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
  22. swarm/blueprints/family_ties/apps.py +11 -0
  23. swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
  24. swarm/blueprints/family_ties/models.py +19 -0
  25. swarm/blueprints/family_ties/serializers.py +7 -0
  26. swarm/blueprints/family_ties/settings.py +16 -0
  27. swarm/blueprints/family_ties/urls.py +10 -0
  28. swarm/blueprints/family_ties/views.py +26 -0
  29. swarm/blueprints/flock/__init__.py +0 -0
  30. swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
  31. swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
  32. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
  33. swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
  34. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
  35. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
  36. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
  37. swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
  38. swarm/blueprints/rue_code/__init__.py +0 -0
  39. swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
  40. swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
  41. swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
  42. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  43. swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
  44. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
  45. swarm/extensions/blueprint/__init__.py +30 -15
  46. swarm/extensions/blueprint/agent_utils.py +16 -40
  47. swarm/extensions/blueprint/blueprint_base.py +141 -543
  48. swarm/extensions/blueprint/blueprint_discovery.py +112 -98
  49. swarm/extensions/blueprint/cli_handler.py +185 -0
  50. swarm/extensions/blueprint/config_loader.py +122 -0
  51. swarm/extensions/blueprint/django_utils.py +181 -79
  52. swarm/extensions/blueprint/interactive_mode.py +1 -1
  53. swarm/extensions/config/config_loader.py +83 -200
  54. swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
  55. swarm/extensions/launchers/swarm_cli.py +199 -287
  56. swarm/llm/chat_completion.py +26 -55
  57. swarm/management/__init__.py +0 -0
  58. swarm/management/commands/__init__.py +0 -0
  59. swarm/management/commands/runserver.py +58 -0
  60. swarm/permissions.py +38 -0
  61. swarm/serializers.py +96 -5
  62. swarm/settings.py +95 -110
  63. swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
  64. swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
  65. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
  66. swarm/static/contrib/markedjs/marked.min.js +6 -0
  67. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
  68. swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
  69. swarm/static/contrib/tabler-icons/archive.svg +21 -0
  70. swarm/static/contrib/tabler-icons/artboard.svg +27 -0
  71. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
  72. swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
  73. swarm/static/contrib/tabler-icons/carambola.svg +19 -0
  74. swarm/static/contrib/tabler-icons/copy.svg +20 -0
  75. swarm/static/contrib/tabler-icons/download.svg +21 -0
  76. swarm/static/contrib/tabler-icons/edit.svg +21 -0
  77. swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
  78. swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
  79. swarm/static/contrib/tabler-icons/headset.svg +22 -0
  80. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
  81. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
  82. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
  83. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
  84. swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
  85. swarm/static/contrib/tabler-icons/message-star.svg +22 -0
  86. swarm/static/contrib/tabler-icons/message-x.svg +23 -0
  87. swarm/static/contrib/tabler-icons/message.svg +21 -0
  88. swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
  89. swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
  90. swarm/static/contrib/tabler-icons/robot.svg +26 -0
  91. swarm/static/contrib/tabler-icons/search.svg +19 -0
  92. swarm/static/contrib/tabler-icons/settings.svg +20 -0
  93. swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
  94. swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
  95. swarm/static/css/dropdown.css +22 -0
  96. swarm/static/htmx/htmx.min.js +0 -0
  97. swarm/static/js/dropdown.js +23 -0
  98. swarm/static/rest_mode/css/base.css +470 -0
  99. swarm/static/rest_mode/css/chat-history.css +286 -0
  100. swarm/static/rest_mode/css/chat.css +251 -0
  101. swarm/static/rest_mode/css/chatbot.css +74 -0
  102. swarm/static/rest_mode/css/chatgpt.css +62 -0
  103. swarm/static/rest_mode/css/colors/corporate.css +74 -0
  104. swarm/static/rest_mode/css/colors/pastel.css +81 -0
  105. swarm/static/rest_mode/css/colors/tropical.css +82 -0
  106. swarm/static/rest_mode/css/general.css +142 -0
  107. swarm/static/rest_mode/css/layout.css +167 -0
  108. swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
  109. swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
  110. swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
  111. swarm/static/rest_mode/css/messages.css +84 -0
  112. swarm/static/rest_mode/css/messenger.css +135 -0
  113. swarm/static/rest_mode/css/settings.css +91 -0
  114. swarm/static/rest_mode/css/simple.css +44 -0
  115. swarm/static/rest_mode/css/slack.css +58 -0
  116. swarm/static/rest_mode/css/style.css +156 -0
  117. swarm/static/rest_mode/css/theme.css +30 -0
  118. swarm/static/rest_mode/css/toast.css +40 -0
  119. swarm/static/rest_mode/js/auth.js +9 -0
  120. swarm/static/rest_mode/js/blueprint.js +41 -0
  121. swarm/static/rest_mode/js/blueprintUtils.js +12 -0
  122. swarm/static/rest_mode/js/chatLogic.js +79 -0
  123. swarm/static/rest_mode/js/debug.js +63 -0
  124. swarm/static/rest_mode/js/events.js +98 -0
  125. swarm/static/rest_mode/js/main.js +19 -0
  126. swarm/static/rest_mode/js/messages.js +264 -0
  127. swarm/static/rest_mode/js/messengerLogic.js +355 -0
  128. swarm/static/rest_mode/js/modules/apiService.js +84 -0
  129. swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
  130. swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
  131. swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
  132. swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
  133. swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
  134. swarm/static/rest_mode/js/modules/state.js +7 -0
  135. swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
  136. swarm/static/rest_mode/js/modules/validation.js +23 -0
  137. swarm/static/rest_mode/js/rendering.js +119 -0
  138. swarm/static/rest_mode/js/settings.js +130 -0
  139. swarm/static/rest_mode/js/sidebar.js +94 -0
  140. swarm/static/rest_mode/js/simpleLogic.js +37 -0
  141. swarm/static/rest_mode/js/slackLogic.js +66 -0
  142. swarm/static/rest_mode/js/splash.js +76 -0
  143. swarm/static/rest_mode/js/theme.js +111 -0
  144. swarm/static/rest_mode/js/toast.js +36 -0
  145. swarm/static/rest_mode/js/ui.js +265 -0
  146. swarm/static/rest_mode/js/validation.js +57 -0
  147. swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
  148. swarm/static/rest_mode/svg/arrow_down.svg +5 -0
  149. swarm/static/rest_mode/svg/arrow_left.svg +5 -0
  150. swarm/static/rest_mode/svg/arrow_right.svg +5 -0
  151. swarm/static/rest_mode/svg/arrow_up.svg +5 -0
  152. swarm/static/rest_mode/svg/attach.svg +8 -0
  153. swarm/static/rest_mode/svg/avatar.svg +7 -0
  154. swarm/static/rest_mode/svg/canvas.svg +6 -0
  155. swarm/static/rest_mode/svg/chat_history.svg +4 -0
  156. swarm/static/rest_mode/svg/close.svg +5 -0
  157. swarm/static/rest_mode/svg/copy.svg +4 -0
  158. swarm/static/rest_mode/svg/dark_mode.svg +3 -0
  159. swarm/static/rest_mode/svg/edit.svg +5 -0
  160. swarm/static/rest_mode/svg/layout.svg +9 -0
  161. swarm/static/rest_mode/svg/logo.svg +29 -0
  162. swarm/static/rest_mode/svg/logout.svg +5 -0
  163. swarm/static/rest_mode/svg/mobile.svg +5 -0
  164. swarm/static/rest_mode/svg/new_chat.svg +4 -0
  165. swarm/static/rest_mode/svg/not_visible.svg +5 -0
  166. swarm/static/rest_mode/svg/plus.svg +7 -0
  167. swarm/static/rest_mode/svg/run_code.svg +6 -0
  168. swarm/static/rest_mode/svg/save.svg +4 -0
  169. swarm/static/rest_mode/svg/search.svg +6 -0
  170. swarm/static/rest_mode/svg/settings.svg +4 -0
  171. swarm/static/rest_mode/svg/speaker.svg +5 -0
  172. swarm/static/rest_mode/svg/stop.svg +6 -0
  173. swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
  174. swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
  175. swarm/static/rest_mode/svg/toggle_off.svg +6 -0
  176. swarm/static/rest_mode/svg/toggle_on.svg +6 -0
  177. swarm/static/rest_mode/svg/trash.svg +10 -0
  178. swarm/static/rest_mode/svg/undo.svg +3 -0
  179. swarm/static/rest_mode/svg/visible.svg +8 -0
  180. swarm/static/rest_mode/svg/voice.svg +10 -0
  181. swarm/templates/account/login.html +22 -0
  182. swarm/templates/account/signup.html +32 -0
  183. swarm/templates/base.html +30 -0
  184. swarm/templates/chat.html +43 -0
  185. swarm/templates/index.html +35 -0
  186. swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
  187. swarm/templates/rest_mode/components/header.html +45 -0
  188. swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
  189. swarm/templates/rest_mode/components/settings_dialog.html +97 -0
  190. swarm/templates/rest_mode/components/splash_screen.html +7 -0
  191. swarm/templates/rest_mode/components/top_bar.html +28 -0
  192. swarm/templates/rest_mode/message_ui.html +50 -0
  193. swarm/templates/rest_mode/slackbot.html +30 -0
  194. swarm/templates/simple_blueprint_page.html +24 -0
  195. swarm/templates/websocket_partials/final_system_message.html +3 -0
  196. swarm/templates/websocket_partials/system_message.html +4 -0
  197. swarm/templates/websocket_partials/user_message.html +5 -0
  198. swarm/urls.py +57 -74
  199. swarm/utils/log_utils.py +63 -0
  200. swarm/views/api_views.py +48 -39
  201. swarm/views/chat_views.py +156 -70
  202. swarm/views/core_views.py +85 -90
  203. swarm/views/model_views.py +64 -121
  204. swarm/views/utils.py +65 -441
  205. open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
  206. open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
  207. open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
  208. open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
  209. swarm/agent/agent.py +0 -49
  210. swarm/core.py +0 -326
  211. swarm/extensions/mcp/__init__.py +0 -1
  212. swarm/extensions/mcp/cache_utils.py +0 -36
  213. swarm/extensions/mcp/mcp_client.py +0 -341
  214. swarm/extensions/mcp/mcp_constants.py +0 -7
  215. swarm/extensions/mcp/mcp_tool_provider.py +0 -110
  216. swarm/types.py +0 -126
  217. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/licenses/LICENSE +0 -0
@@ -1,304 +1,216 @@
1
- #!/usr/bin/env python3
2
- import argparse
3
- import importlib.util
4
1
  import os
5
- import sys
2
+ import typer
3
+ import platformdirs
6
4
  import subprocess
7
- import shutil
8
- import json
9
- import PyInstaller.__main__
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
10
24
 
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
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)
20
29
 
21
- MANAGED_DIR = os.path.expanduser("~/.swarm/blueprints")
22
- BIN_DIR = os.path.expanduser("~/.swarm/bin")
23
30
 
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)
31
+ # --- Typer App ---
32
+ app = typer.Typer(
33
+ help="Swarm CLI tool for managing blueprints.",
34
+ add_completion=True # Enable shell completion commands
35
+ )
29
36
 
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}.")
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
62
45
 
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.")
46
+ # --- CLI Commands ---
73
47
 
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)
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)
82
65
 
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)
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
104
92
 
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
93
  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)}")
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
+
125
111
 
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)
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)
156
121
 
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":
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
225
172
  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:
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():
241
199
  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)
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
+
302
213
 
214
+ # --- Main Execution Guard ---
303
215
  if __name__ == "__main__":
304
- main()
216
+ app()