open-swarm 0.1.1745125933__py3-none-any.whl → 0.1.1745126277__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 (52) hide show
  1. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/METADATA +12 -8
  2. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/RECORD +52 -25
  3. swarm/blueprints/README.md +19 -18
  4. swarm/blueprints/blueprint_audit_status.json +1 -1
  5. swarm/blueprints/chatbot/blueprint_chatbot.py +160 -72
  6. swarm/blueprints/codey/README.md +88 -8
  7. swarm/blueprints/codey/blueprint_codey.py +1116 -210
  8. swarm/blueprints/codey/codey_cli.py +10 -0
  9. swarm/blueprints/codey/session_logs/session_2025-04-19T01-15-31.md +17 -0
  10. swarm/blueprints/codey/session_logs/session_2025-04-19T01-16-03.md +17 -0
  11. swarm/blueprints/common/operation_box_utils.py +83 -0
  12. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +21 -298
  13. swarm/blueprints/divine_code/blueprint_divine_code.py +182 -9
  14. swarm/blueprints/django_chat/blueprint_django_chat.py +150 -24
  15. swarm/blueprints/echocraft/blueprint_echocraft.py +142 -13
  16. swarm/blueprints/geese/README.md +97 -0
  17. swarm/blueprints/geese/blueprint_geese.py +677 -93
  18. swarm/blueprints/geese/geese_cli.py +102 -0
  19. swarm/blueprints/jeeves/blueprint_jeeves.py +712 -0
  20. swarm/blueprints/jeeves/jeeves_cli.py +55 -0
  21. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +109 -22
  22. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +172 -40
  23. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +79 -41
  24. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +82 -35
  25. swarm/blueprints/omniplex/blueprint_omniplex.py +56 -24
  26. swarm/blueprints/poets/blueprint_poets.py +141 -100
  27. swarm/blueprints/poets/poets_cli.py +23 -0
  28. swarm/blueprints/rue_code/README.md +8 -0
  29. swarm/blueprints/rue_code/blueprint_rue_code.py +188 -20
  30. swarm/blueprints/rue_code/rue_code_cli.py +43 -0
  31. swarm/blueprints/stewie/apps.py +12 -0
  32. swarm/blueprints/stewie/blueprint_family_ties.py +349 -0
  33. swarm/blueprints/stewie/models.py +19 -0
  34. swarm/blueprints/stewie/serializers.py +10 -0
  35. swarm/blueprints/stewie/settings.py +17 -0
  36. swarm/blueprints/stewie/urls.py +11 -0
  37. swarm/blueprints/stewie/views.py +26 -0
  38. swarm/blueprints/suggestion/blueprint_suggestion.py +54 -39
  39. swarm/blueprints/whinge_surf/README.md +22 -0
  40. swarm/blueprints/whinge_surf/__init__.py +1 -0
  41. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +565 -0
  42. swarm/blueprints/whinge_surf/whinge_surf_cli.py +99 -0
  43. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +66 -37
  44. swarm/blueprints/zeus/__init__.py +2 -0
  45. swarm/blueprints/zeus/apps.py +4 -0
  46. swarm/blueprints/zeus/blueprint_zeus.py +270 -0
  47. swarm/blueprints/zeus/zeus_cli.py +13 -0
  48. swarm/cli/async_input.py +65 -0
  49. swarm/cli/async_input_demo.py +32 -0
  50. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/WHEEL +0 -0
  51. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/entry_points.txt +0 -0
  52. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,7 @@ import logging
8
8
  import os
9
9
  import sys
10
10
  from typing import Dict, Any, List, ClassVar, Optional
11
+ from swarm.blueprints.common.operation_box_utils import display_operation_box
11
12
 
12
13
  # Ensure src is in path for BlueprintBase import
13
14
  project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
@@ -24,6 +25,10 @@ try:
24
25
  from openai import AsyncOpenAI
25
26
  from swarm.core.blueprint_base import BlueprintBase
26
27
  from swarm.core.blueprint_ux import BlueprintUX
28
+ from rich.console import Console
29
+ from rich.text import Text
30
+ from rich.style import Style
31
+ import threading, time
27
32
  except ImportError as e:
28
33
  print(f"ERROR: Import failed in DivineOpsBlueprint: {e}. Check 'openai-agents' install and project structure.")
29
34
  print(f"sys.path: {sys.path}")
@@ -121,8 +126,84 @@ Available MCP Tools (if provided): sequential-thinking, filesystem.
121
126
  You also have fileops capabilities: read_file, write_file, list_files, execute_shell_command.
122
127
  """
123
128
 
124
- # Spinner UX enhancement (Open Swarm TODO)
125
- SPINNER_STATES = ['Generating.', 'Generating..', 'Generating...', 'Running...']
129
+ # --- Spinner and ANSI/emoji operation box for unified UX ---
130
+ from swarm.ux.ansi_box import ansi_box
131
+ from rich.console import Console
132
+ from rich.style import Style
133
+ from rich.text import Text
134
+
135
+ # Patch Spinner to match Open Swarm UX (FRAMES, slow warning, etc.)
136
+ class DivineOpsSpinner:
137
+ FRAMES = [
138
+ "Generating.", "Generating..", "Generating...", "Running...",
139
+ "⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
140
+ "⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
141
+ "⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
142
+ ]
143
+ SLOW_FRAME = "Generating... Taking longer than expected"
144
+ INTERVAL = 0.12
145
+ SLOW_THRESHOLD = 10 # seconds
146
+
147
+ def __init__(self):
148
+ import threading, time
149
+ self._stop_event = threading.Event()
150
+ self._thread = None
151
+ self._start_time = None
152
+ self.console = Console()
153
+ self._last_frame = None
154
+ self._last_slow = False
155
+
156
+ def start(self):
157
+ import time
158
+ self._stop_event.clear()
159
+ self._start_time = time.time()
160
+ import threading
161
+ self._thread = threading.Thread(target=self._spin, daemon=True)
162
+ self._thread.start()
163
+
164
+ def _spin(self):
165
+ import time
166
+ idx = 0
167
+ while not self._stop_event.is_set():
168
+ elapsed = time.time() - self._start_time
169
+ if elapsed > self.SLOW_THRESHOLD:
170
+ txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
171
+ self._last_frame = self.SLOW_FRAME
172
+ self._last_slow = True
173
+ else:
174
+ frame = self.FRAMES[idx % len(self.FRAMES)]
175
+ txt = Text(frame, style=Style(color="cyan", bold=True))
176
+ self._last_frame = frame
177
+ self._last_slow = False
178
+ self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
179
+ time.sleep(self.INTERVAL)
180
+ idx += 1
181
+ self.console.print(" " * 40, end="\r") # Clear line
182
+
183
+ def stop(self, final_message="Done!"):
184
+ self._stop_event.set()
185
+ if self._thread:
186
+ self._thread.join()
187
+ self.console.print(Text(final_message, style=Style(color="green", bold=True)))
188
+
189
+ def current_spinner_state(self):
190
+ if self._last_slow:
191
+ return self.SLOW_FRAME
192
+ return self._last_frame or self.FRAMES[0]
193
+
194
+
195
+ def print_operation_box(op_type, results, params=None, result_type="divine", taking_long=False):
196
+ emoji = "⚡" if result_type == "divine" else "🔍"
197
+ style = 'success' if result_type == "divine" else 'default'
198
+ box_title = op_type if op_type else ("Divine Output" if result_type == "divine" else "Results")
199
+ summary_lines = []
200
+ count = len(results) if isinstance(results, list) else 0
201
+ summary_lines.append(f"Results: {count}")
202
+ if params:
203
+ for k, v in params.items():
204
+ summary_lines.append(f"{k.capitalize()}: {v}")
205
+ box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
206
+ ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
126
207
 
127
208
  # --- FileOps Tool Logic Definitions ---
128
209
  # Patch: Expose underlying fileops functions for direct testing
@@ -149,12 +230,28 @@ def list_files(directory: str = '.') -> str:
149
230
  except Exception as e:
150
231
  return f"ERROR: {e}"
151
232
  def execute_shell_command(command: str) -> str:
152
- import subprocess
233
+ """
234
+ Executes a shell command and returns its stdout and stderr.
235
+ Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
236
+ """
237
+ logger.info(f"Executing shell command: {command}")
153
238
  try:
154
- result = subprocess.run(command, shell=True, capture_output=True, text=True)
155
- return result.stdout + result.stderr
239
+ import os
240
+ timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
241
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
242
+ output = f"Exit Code: {result.returncode}\n"
243
+ if result.stdout:
244
+ output += f"STDOUT:\n{result.stdout}\n"
245
+ if result.stderr:
246
+ output += f"STDERR:\n{result.stderr}\n"
247
+ logger.info(f"Command finished. Exit Code: {result.returncode}")
248
+ return output.strip()
249
+ except subprocess.TimeoutExpired:
250
+ logger.error(f"Command timed out: {command}")
251
+ return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
156
252
  except Exception as e:
157
- return f"ERROR: {e}"
253
+ logger.error(f"Error executing command '{command}': {e}", exc_info=True)
254
+ return f"Error executing command: {e}"
158
255
  read_file_tool = PatchedFunctionTool(read_file, 'read_file')
159
256
  write_file_tool = PatchedFunctionTool(write_file, 'write_file')
160
257
  list_files_tool = PatchedFunctionTool(list_files, 'list_files')
@@ -166,6 +263,36 @@ class DivineOpsBlueprint(BlueprintBase):
166
263
  super().__init__(blueprint_id, config_path=config_path, **kwargs)
167
264
  # Use serious style for DivineOps
168
265
  self.ux = BlueprintUX(style="serious")
266
+ # Spinner for pantheon operations
267
+ self._spinner = None
268
+
269
+ class Spinner:
270
+ FRAMES = [
271
+ "⚡ Summoning Pantheon...",
272
+ "🔥 Forging Tools...",
273
+ "🌩️ Commanding Zeus...",
274
+ "✨ Awakening Divinity..."
275
+ ]
276
+ INTERVAL = 0.15
277
+ def __init__(self):
278
+ self._stop = threading.Event()
279
+ self._thread = threading.Thread(target=self._spin, daemon=True)
280
+ self._idx = 0
281
+ self.console = Console()
282
+ def start(self):
283
+ self._stop.clear()
284
+ self._thread.start()
285
+ def _spin(self):
286
+ while not self._stop.is_set():
287
+ frame = DivineOpsBlueprint.Spinner.FRAMES[self._idx % len(DivineOpsBlueprint.Spinner.FRAMES)]
288
+ self.console.print(Text(frame, style=Style(color="magenta", bold=True)), end="\r")
289
+ self._idx += 1
290
+ time.sleep(DivineOpsBlueprint.Spinner.INTERVAL)
291
+ self.console.print(" " * 40, end="\r")
292
+ def stop(self, final="🌟 All pantheon tasks complete!"):
293
+ self._stop.set()
294
+ self._thread.join()
295
+ self.console.print(Text(final, style=Style(color="green", bold=True)))
169
296
 
170
297
  """ Divine Ops: Streamlined Software Dev & Sysadmin Team Blueprint using openai-agents """
171
298
  metadata: ClassVar[Dict[str, Any]] = {
@@ -282,11 +409,16 @@ class DivineOpsBlueprint(BlueprintBase):
282
409
  return zeus_agent
283
410
 
284
411
  async def run(self, messages: List[Dict[str, Any]], **kwargs) -> Any:
285
- """Main execution entry point for the DivineOps blueprint."""
412
+ """Main execution entry point for the DivineOps blueprint with ANSI spinner."""
286
413
  logger.info("DivineOpsBlueprint run method called.")
414
+ # Start spinner
415
+ self._spinner = DivineOpsSpinner()
416
+ self._spinner.start()
287
417
  instruction = messages[-1].get("content", "") if messages else ""
288
418
  async for chunk in self._run_non_interactive(instruction, **kwargs):
289
419
  yield chunk
420
+ # Stop spinner and show completion
421
+ self._spinner.stop()
290
422
  logger.info("DivineOpsBlueprint run method finished.")
291
423
 
292
424
  async def _run_non_interactive(self, instruction: str, **kwargs) -> Any:
@@ -302,6 +434,18 @@ class DivineOpsBlueprint(BlueprintBase):
302
434
  logger.error(f"Error during non-interactive run: {e}", exc_info=True)
303
435
  yield { "messages": [ {"role": "assistant", "content": f"An error occurred: {e}"} ] }
304
436
 
437
+ class ZeusBlueprint(BlueprintBase):
438
+ def __init__(self, blueprint_id: str = "zeus", config=None, config_path=None, **kwargs):
439
+ super().__init__()
440
+ self.blueprint_id = blueprint_id
441
+ self.config_path = config_path
442
+ self._config = config if config is not None else None
443
+ self._llm_profile_name = None
444
+ self._llm_profile_data = None
445
+ self._markdown_output = None
446
+ # Add other attributes as needed for Zeus
447
+ # ...
448
+
305
449
  # Standard Python entry point
306
450
  if __name__ == "__main__":
307
451
  import asyncio
@@ -312,6 +456,35 @@ if __name__ == "__main__":
312
456
  ]
313
457
  blueprint = DivineOpsBlueprint(blueprint_id="demo-1")
314
458
  async def run_and_print():
315
- async for response in blueprint.run(messages):
316
- print(json.dumps(response, indent=2))
459
+ spinner = DivineOpsSpinner()
460
+ spinner.start()
461
+ try:
462
+ all_results = []
463
+ async for response in blueprint.run(messages):
464
+ content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
465
+ all_results.append(content)
466
+ # Enhanced progressive output
467
+ if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
468
+ display_operation_box(
469
+ title="Progressive Operation",
470
+ content="\n".join(response.get("matches", [])),
471
+ style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
472
+ result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
473
+ params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
474
+ progress_line=response.get('progress'),
475
+ total_lines=response.get('total'),
476
+ spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
477
+ op_type=response.get("type", "search"),
478
+ emoji="🔍" if response.get("type") == "code_search" else "🧠"
479
+ )
480
+ finally:
481
+ spinner.stop()
482
+ display_operation_box(
483
+ title="Divine Output",
484
+ content="\n".join(all_results),
485
+ style="bold green",
486
+ result_count=len(all_results),
487
+ params={"prompt": messages[0]["content"]},
488
+ op_type="divine"
489
+ )
317
490
  asyncio.run(run_and_print())
@@ -9,6 +9,9 @@ import logging
9
9
  import sys
10
10
  import os
11
11
  from typing import Dict, Any, List
12
+ from swarm.blueprints.common.operation_box_utils import display_operation_box
13
+ from swarm.core.blueprint_ux import BlueprintUXImproved
14
+ import time
12
15
 
13
16
  # --- Logging Setup ---
14
17
  def setup_logging():
@@ -49,9 +52,78 @@ from swarm.utils.logger_setup import setup_logger
49
52
 
50
53
  logger = setup_logger(__name__)
51
54
 
55
+ # --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
56
+ from swarm.ux.ansi_box import ansi_box
57
+ from rich.console import Console
58
+ from rich.style import Style
59
+ from rich.text import Text
60
+ import threading
61
+ import time
62
+
63
+ class DjangoChatSpinner:
64
+ FRAMES = [
65
+ "Generating.", "Generating..", "Generating...", "Running...",
66
+ "⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
67
+ "⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
68
+ "⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
69
+ ]
70
+ SLOW_FRAME = "Generating... Taking longer than expected"
71
+ INTERVAL = 0.12
72
+ SLOW_THRESHOLD = 10 # seconds
73
+
74
+ def __init__(self):
75
+ self._stop_event = threading.Event()
76
+ self._thread = None
77
+ self._start_time = None
78
+ self.console = Console()
79
+ self._last_frame = None
80
+ self._last_slow = False
81
+
82
+ def start(self):
83
+ self._stop_event.clear()
84
+ self._start_time = time.time()
85
+ self._thread = threading.Thread(target=self._spin, daemon=True)
86
+ self._thread.start()
87
+
88
+ def _spin(self):
89
+ idx = 0
90
+ while not self._stop_event.is_set():
91
+ elapsed = time.time() - self._start_time
92
+ if elapsed > self.SLOW_THRESHOLD:
93
+ txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
94
+ self._last_frame = self.SLOW_FRAME
95
+ self._last_slow = True
96
+ else:
97
+ frame = self.FRAMES[idx % len(self.FRAMES)]
98
+ txt = Text(frame, style=Style(color="cyan", bold=True))
99
+ self._last_frame = frame
100
+ self._last_slow = False
101
+ self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
102
+ time.sleep(self.INTERVAL)
103
+ idx += 1
104
+ self.console.print(" " * 40, end="\r") # Clear line
105
+
106
+ def stop(self, final_message="Done!"):
107
+ self._stop_event.set()
108
+ if self._thread:
109
+ self._thread.join()
110
+ self.console.print(Text(final_message, style=Style(color="green", bold=True)))
111
+
112
+ def current_spinner_state(self):
113
+ if self._last_slow:
114
+ return self.SLOW_FRAME
115
+ return self._last_frame or self.FRAMES[0]
116
+
117
+
52
118
  class DjangoChatBlueprint(Blueprint):
53
- def __init__(self, *args, **kwargs):
54
- super().__init__(*args, **kwargs)
119
+ def __init__(self, blueprint_id: str = "django_chat", config=None, config_path=None, **kwargs):
120
+ super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
121
+ self.blueprint_id = blueprint_id
122
+ self.config_path = config_path
123
+ self._config = config if config is not None else None
124
+ self._llm_profile_name = None
125
+ self._llm_profile_data = None
126
+ self._markdown_output = None
55
127
  class DummyLLM:
56
128
  def chat_completion_stream(self, messages, **_):
57
129
  class DummyStream:
@@ -100,26 +172,51 @@ class DjangoChatBlueprint(Blueprint):
100
172
  def render_prompt(self, template_name: str, context: dict) -> str:
101
173
  return f"User request: {context.get('user_request', '')}\nHistory: {context.get('history', '')}\nAvailable tools: {', '.join(context.get('available_tools', []))}"
102
174
 
103
- async def run(self, messages: List[Dict[str, str]]) -> object:
104
- last_user_message = next((m['content'] for m in reversed(messages) if m['role'] == 'user'), None)
105
- if not last_user_message:
106
- yield {"messages": [{"role": "assistant", "content": "I need a user message to proceed."}]}
107
- return
108
- prompt_context = {
109
- "user_request": last_user_message,
110
- "history": messages[:-1],
111
- "available_tools": ["django_chat"]
112
- }
113
- rendered_prompt = self.render_prompt("django_chat_prompt.j2", prompt_context)
114
- yield {
115
- "messages": [
116
- {
117
- "role": "assistant",
118
- "content": f"[DjangoChat LLM] Would respond to: {rendered_prompt}"
119
- }
120
- ]
121
- }
122
- return
175
+ async def run(self, messages: List[Dict[str, str]]):
176
+ """Main execution entry point for the DjangoChat blueprint."""
177
+ logger.info("DjangoChatBlueprint run method called.")
178
+ instruction = messages[-1].get("content", "") if messages else ""
179
+ ux = BlueprintUXImproved(style="serious")
180
+ spinner_idx = 0
181
+ start_time = time.time()
182
+ spinner_yield_interval = 1.0 # seconds
183
+ last_spinner_time = start_time
184
+ yielded_spinner = False
185
+ result_chunks = []
186
+ try:
187
+ # Simulate agent runner pattern (replace with actual agent logic if available)
188
+ prompt_context = {
189
+ "user_request": instruction,
190
+ "history": messages[:-1],
191
+ "available_tools": ["django_chat"]
192
+ }
193
+ rendered_prompt = self.render_prompt("django_chat_prompt.j2", prompt_context)
194
+ # Simulate progressive spinner for a few cycles
195
+ for _ in range(3):
196
+ now = time.time()
197
+ if now - last_spinner_time >= spinner_yield_interval:
198
+ taking_long = (now - start_time > 10)
199
+ spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
200
+ yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
201
+ spinner_idx += 1
202
+ last_spinner_time = now
203
+ yielded_spinner = True
204
+ await asyncio.sleep(0.2)
205
+ # Final result
206
+ summary = ux.summary("Operation", 1, {"instruction": instruction[:40]})
207
+ box = ux.ansi_emoji_box(
208
+ title="DjangoChat Result",
209
+ content=f"[DjangoChat LLM] Would respond to: {rendered_prompt}",
210
+ summary=summary,
211
+ params={"instruction": instruction[:40]},
212
+ result_count=1,
213
+ op_type="run",
214
+ status="success"
215
+ )
216
+ yield {"messages": [{"role": "assistant", "content": box}]}
217
+ except Exception as e:
218
+ logger.error(f"Error during DjangoChat run: {e}", exc_info=True)
219
+ yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
123
220
 
124
221
  def run_with_context(self, messages: List[Dict[str, str]], context_variables: dict) -> dict:
125
222
  """Minimal implementation for CLI compatibility without agents."""
@@ -137,6 +234,35 @@ if __name__ == "__main__":
137
234
  ]
138
235
  blueprint = DjangoChatBlueprint(blueprint_id="demo-1")
139
236
  async def run_and_print():
140
- async for response in blueprint.run(messages):
141
- print(json.dumps(response, indent=2))
237
+ spinner = DjangoChatSpinner()
238
+ spinner.start()
239
+ try:
240
+ all_results = []
241
+ async for response in blueprint.run(messages):
242
+ content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
243
+ all_results.append(content)
244
+ # Enhanced progressive output
245
+ if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
246
+ display_operation_box(
247
+ title="Progressive Operation",
248
+ content="\n".join(response.get("matches", [])),
249
+ style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
250
+ result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
251
+ params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
252
+ progress_line=response.get('progress'),
253
+ total_lines=response.get('total'),
254
+ spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
255
+ op_type=response.get("type", "search"),
256
+ emoji="🔍" if response.get("type") == "code_search" else "🧠"
257
+ )
258
+ finally:
259
+ spinner.stop()
260
+ display_operation_box(
261
+ title="DjangoChat Output",
262
+ content="\n".join(all_results),
263
+ style="bold green",
264
+ result_count=len(all_results),
265
+ params={"prompt": messages[0]["content"]},
266
+ op_type="django_chat"
267
+ )
142
268
  asyncio.run(run_and_print())
@@ -36,12 +36,28 @@ def list_files(directory: str = '.') -> str:
36
36
  except Exception as e:
37
37
  return f"ERROR: {e}"
38
38
  def execute_shell_command(command: str) -> str:
39
- import subprocess
39
+ """
40
+ Executes a shell command and returns its stdout and stderr.
41
+ Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
42
+ """
43
+ logger.info(f"Executing shell command: {command}")
40
44
  try:
41
- result = subprocess.run(command, shell=True, capture_output=True, text=True)
42
- return result.stdout + result.stderr
45
+ import os
46
+ timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
47
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
48
+ output = f"Exit Code: {result.returncode}\n"
49
+ if result.stdout:
50
+ output += f"STDOUT:\n{result.stdout}\n"
51
+ if result.stderr:
52
+ output += f"STDERR:\n{result.stderr}\n"
53
+ logger.info(f"Command finished. Exit Code: {result.returncode}")
54
+ return output.strip()
55
+ except subprocess.TimeoutExpired:
56
+ logger.error(f"Command timed out: {command}")
57
+ return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
43
58
  except Exception as e:
44
- return f"ERROR: {e}"
59
+ logger.error(f"Error executing command '{command}': {e}", exc_info=True)
60
+ return f"Error executing command: {e}"
45
61
  read_file_tool = PatchedFunctionTool(read_file, 'read_file')
46
62
  write_file_tool = PatchedFunctionTool(write_file, 'write_file')
47
63
  list_files_tool = PatchedFunctionTool(list_files, 'list_files')
@@ -67,8 +83,16 @@ Self-healing, fileops-enabled, swarm-scalable.
67
83
  # rue_code error handling: try/except ImportError with sys.exit(1)
68
84
 
69
85
  class EchoCraftBlueprint(BlueprintBase):
70
- def __init__(self, blueprint_id: str, config_path: Optional[Path] = None, **kwargs):
71
- super().__init__(blueprint_id, config_path=config_path, **kwargs)
86
+ def __init__(self, blueprint_id: str = "echocraft", config=None, config_path=None, **kwargs):
87
+ super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
88
+ self.blueprint_id = blueprint_id
89
+ self.config_path = config_path
90
+ self._config = config if config is not None else {}
91
+ self._llm_profile_name = None
92
+ self._llm_profile_data = None
93
+ self._markdown_output = None
94
+ # Add other attributes as needed for Echocraft
95
+ # ...
72
96
 
73
97
  """
74
98
  A simple blueprint that echoes the last user message.
@@ -99,13 +123,29 @@ class EchoCraftBlueprint(BlueprintBase):
99
123
  return '\n'.join(os.listdir(directory))
100
124
  except Exception as e:
101
125
  return f"ERROR: {e}"
102
- def execute_shell_command(command: str) -> str:
103
- import subprocess
126
+ def execute_shell_command(self, command: str) -> str:
127
+ """
128
+ Executes a shell command and returns its stdout and stderr.
129
+ Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
130
+ """
131
+ logger.info(f"Executing shell command: {command}")
104
132
  try:
105
- result = subprocess.run(command, shell=True, capture_output=True, text=True)
106
- return result.stdout + result.stderr
133
+ import os
134
+ timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
135
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
136
+ output = f"Exit Code: {result.returncode}\n"
137
+ if result.stdout:
138
+ output += f"STDOUT:\n{result.stdout}\n"
139
+ if result.stderr:
140
+ output += f"STDERR:\n{result.stderr}\n"
141
+ logger.info(f"Command finished. Exit Code: {result.returncode}")
142
+ return output.strip()
143
+ except subprocess.TimeoutExpired:
144
+ logger.error(f"Command timed out: {command}")
145
+ return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
107
146
  except Exception as e:
108
- return f"ERROR: {e}"
147
+ logger.error(f"Error executing command '{command}': {e}", exc_info=True)
148
+ return f"Error executing command: {e}"
109
149
  read_file_tool = PatchedFunctionTool(read_file, 'read_file')
110
150
  write_file_tool = PatchedFunctionTool(write_file, 'write_file')
111
151
  list_files_tool = PatchedFunctionTool(list_files, 'list_files')
@@ -241,6 +281,82 @@ class EchoCraftBlueprint(BlueprintBase):
241
281
  )
242
282
  return echo_agent
243
283
 
284
+ # --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
285
+ from swarm.ux.ansi_box import ansi_box
286
+ from rich.console import Console
287
+ from rich.style import Style
288
+ from rich.text import Text
289
+ import threading
290
+ import time
291
+
292
+ class EchoCraftSpinner:
293
+ FRAMES = [
294
+ "Generating.", "Generating..", "Generating...", "Running...",
295
+ "⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
296
+ "⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
297
+ "⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
298
+ ]
299
+ SLOW_FRAME = "Generating... Taking longer than expected"
300
+ INTERVAL = 0.12
301
+ SLOW_THRESHOLD = 10 # seconds
302
+
303
+ def __init__(self):
304
+ self._stop_event = threading.Event()
305
+ self._thread = None
306
+ self._start_time = None
307
+ self.console = Console()
308
+ self._last_frame = None
309
+ self._last_slow = False
310
+
311
+ def start(self):
312
+ self._stop_event.clear()
313
+ self._start_time = time.time()
314
+ self._thread = threading.Thread(target=self._spin, daemon=True)
315
+ self._thread.start()
316
+
317
+ def _spin(self):
318
+ idx = 0
319
+ while not self._stop_event.is_set():
320
+ elapsed = time.time() - self._start_time
321
+ if elapsed > self.SLOW_THRESHOLD:
322
+ txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
323
+ self._last_frame = self.SLOW_FRAME
324
+ self._last_slow = True
325
+ else:
326
+ frame = self.FRAMES[idx % len(self.FRAMES)]
327
+ txt = Text(frame, style=Style(color="cyan", bold=True))
328
+ self._last_frame = frame
329
+ self._last_slow = False
330
+ self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
331
+ time.sleep(self.INTERVAL)
332
+ idx += 1
333
+ self.console.print(" " * 40, end="\r") # Clear line
334
+
335
+ def stop(self, final_message="Done!"):
336
+ self._stop_event.set()
337
+ if self._thread:
338
+ self._thread.join()
339
+ self.console.print(Text(final_message, style=Style(color="green", bold=True)))
340
+
341
+ def current_spinner_state(self):
342
+ if self._last_slow:
343
+ return self.SLOW_FRAME
344
+ return self._last_frame or self.FRAMES[0]
345
+
346
+
347
+ def print_operation_box(op_type, results, params=None, result_type="echo", taking_long=False):
348
+ emoji = "🗣️" if result_type == "echo" else "🔍"
349
+ style = 'success' if result_type == "echo" else 'default'
350
+ box_title = op_type if op_type else ("EchoCraft Output" if result_type == "echo" else "Results")
351
+ summary_lines = []
352
+ count = len(results) if isinstance(results, list) else 0
353
+ summary_lines.append(f"Results: {count}")
354
+ if params:
355
+ for k, v in params.items():
356
+ summary_lines.append(f"{k.capitalize()}: {v}")
357
+ box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
358
+ ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
359
+
244
360
  if __name__ == "__main__":
245
361
  import asyncio
246
362
  import json
@@ -250,6 +366,19 @@ if __name__ == "__main__":
250
366
  ]
251
367
  blueprint = EchoCraftBlueprint(blueprint_id="demo-1")
252
368
  async def run_and_print():
253
- async for response in blueprint.run(messages):
254
- print(json.dumps(response, indent=2))
369
+ spinner = EchoCraftSpinner()
370
+ spinner.start()
371
+ try:
372
+ all_results = []
373
+ async for response in blueprint.run(messages):
374
+ content = response["messages"][0]["content"]
375
+ all_results.append(content)
376
+ finally:
377
+ spinner.stop()
378
+ print_operation_box(
379
+ op_type="EchoCraft Output",
380
+ results=all_results,
381
+ params={"prompt": messages[0]["content"]},
382
+ result_type="echo"
383
+ )
255
384
  asyncio.run(run_and_print())