open-swarm 0.1.1745274976__py3-none-any.whl → 0.1.1745275181__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-swarm
3
- Version: 0.1.1745274976
3
+ Version: 0.1.1745275181
4
4
  Summary: Open Swarm: Orchestrating AI Agent Swarms with Django
5
5
  Project-URL: Homepage, https://github.com/yourusername/open-swarm
6
6
  Project-URL: Documentation, https://github.com/yourusername/open-swarm/blob/main/README.md
@@ -47,8 +47,9 @@ swarm/blueprints/flock/README.md,sha256=kiwNc3qshVFfD76GirTlKCuKmWVgqf5v8dM8UTo-
47
47
  swarm/blueprints/flock/__init__.py,sha256=xJE1nyHUagJzrF9xwJAU0q6kAPd_We_jf0ig4YCa7E4,689
48
48
  swarm/blueprints/flock/blueprint_flock.py,sha256=Vg1tOD8LxCOp_YBhNeXLbvw_aOfXrV_eHXZhSxZds3o,99
49
49
  swarm/blueprints/flock/test_basic.py,sha256=kv0n6JYoYKL-VlASiDbJBAmYJ5TATrEscc9Tzm7_E4M,115
50
- swarm/blueprints/geese/README.md,sha256=o5tgiL7cK0m0JdHxMuZ-u8iKuzVO7EQKkuW4gP2mkHU,3699
51
- swarm/blueprints/geese/blueprint_geese.py,sha256=fSf8pmXOCDrW4FQ2S_yX9L62xJYzxntgdlkWY55RjvI,37255
50
+ swarm/blueprints/geese/README.md,sha256=gE37yl-q9Xp9uVaMGivfbl0z0n-2eYEnJJaAQaFhd68,370
51
+ swarm/blueprints/geese/__init__.py,sha256=Wq-UfdMccUT5_9pEyyeJi9fPcIt2RZp1CHAAbBdbHuA,689
52
+ swarm/blueprints/geese/blueprint_geese.py,sha256=AJKUcY5OReFwpO7zk-RFqsD3jqqZMAKaBsTWRlzPXfY,16442
52
53
  swarm/blueprints/geese/geese_cli.py,sha256=JNx2Te4F-HvYnCldXOtzDRKyNznse2N8_LXPlpZ_vbk,5354
53
54
  swarm/blueprints/jeeves/README.md,sha256=yXglrTCfD4SFDqFo4qwo54cIf1ie4ykjUtKlRAE_NhE,1373
54
55
  swarm/blueprints/jeeves/blueprint_jeeves.py,sha256=Tyw2vDFthRaor9EAkXI2GQwOhqYUqBatoYzOnfmnORk,33110
@@ -311,8 +312,8 @@ swarm/views/message_views.py,sha256=sDUnXyqKXC8WwIIMAlWf00s2_a2T9c75Na5FvYMJwBM,
311
312
  swarm/views/model_views.py,sha256=aAbU4AZmrOTaPeKMWtoKK7FPYHdaN3Zbx55JfKzYTRY,2937
312
313
  swarm/views/utils.py,sha256=8Usc0g0L0NPegNAyY20tJBNBy-JLwODf4VmxV0yUtpw,3627
313
314
  swarm/views/web_views.py,sha256=T1CKe-Nyv1C8aDt6QFTGWo_dkH7ojWAvS_QW9mZnZp0,7371
314
- open_swarm-0.1.1745274976.dist-info/METADATA,sha256=bGiJfKLBJX-9mF_aKoJqINZU6cxrHzolMgQ_kbPs7Oc,39330
315
- open_swarm-0.1.1745274976.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
316
- open_swarm-0.1.1745274976.dist-info/entry_points.txt,sha256=fo28d0_zJrytRsh8QqkdlWQT_9lyAwYUx1WuSTDI3HM,177
317
- open_swarm-0.1.1745274976.dist-info/licenses/LICENSE,sha256=BU9bwRlnOt_JDIb6OT55Q4leLZx9RArDLTFnlDIrBEI,1062
318
- open_swarm-0.1.1745274976.dist-info/RECORD,,
315
+ open_swarm-0.1.1745275181.dist-info/METADATA,sha256=cWHyVc3YTjtl7nBr07PrxMvW4MGabSRnOuqBk4lVaf0,39330
316
+ open_swarm-0.1.1745275181.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
317
+ open_swarm-0.1.1745275181.dist-info/entry_points.txt,sha256=fo28d0_zJrytRsh8QqkdlWQT_9lyAwYUx1WuSTDI3HM,177
318
+ open_swarm-0.1.1745275181.dist-info/licenses/LICENSE,sha256=BU9bwRlnOt_JDIb6OT55Q4leLZx9RArDLTFnlDIrBEI,1062
319
+ open_swarm-0.1.1745275181.dist-info/RECORD,,
@@ -1,97 +1,10 @@
1
1
  # Geese Blueprint
2
2
 
3
- **Geese** is special because it brings fun and whimsy to Open Swarm through animated bird prompts and playful interactions. Perfect for lightening the mood while you work!
3
+ This is the README stub for the Geese blueprint.
4
4
 
5
- ## Special Feature
6
- - **Fun Bird Animation Prompts:** Enjoy unique, animated geese interactions and prompts that make your workflow more enjoyable.
5
+ - **Purpose:** Collaborative agent team for Open Swarm.
6
+ - **Required Env Vars:** _Document if any._
7
+ - **Tests:** See `tests/blueprints/test_geese.py` (if exists).
8
+ - **Usage:** `swarm-cli run geese --instruction "ping"`
7
9
 
8
- ---
9
-
10
- A collaborative story writing blueprint for Open Swarm that leverages multiple specialized agents to create, edit, and refine stories.
11
-
12
- ## Features
13
-
14
- ### Enhanced UI/UX
15
- - 🎨 Rich ANSI/emoji boxes for operation feedback
16
- - 📊 Dynamic result counts and search parameters
17
- - ⏳ Intelligent progress spinners with state tracking
18
- - ⚡ Real-time line number updates for long operations
19
- - 🔄 Smart status messages for extended operations
20
-
21
- ### Core Capabilities
22
- - 📋 Plan maintenance and tracking
23
- - 📝 Multi-agent collaboration:
24
- - Planner Agent: Story structure and task delegation
25
- - Writer Agent: Content creation and development
26
- - Editor Agent: Review and refinement
27
- - Coordinator Agent: Process orchestration
28
- - 🔍 Advanced search and analysis operations
29
- - 🎯 Error handling and reflection
30
-
31
- ## Usage
32
-
33
- ```bash
34
- # Basic story generation
35
- swarm geese "Write a story about a magical forest"
36
-
37
- # Interactive mode with file output
38
- swarm geese -i input.txt -o output.txt --interactive
39
-
40
- # Advanced mode with custom parameters
41
- swarm geese --model gpt-4 --temperature 0.7 --max-tokens 4096 "Write an epic fantasy"
42
- ```
43
-
44
- ## Configuration
45
-
46
- The blueprint supports various configuration options:
47
- - Model selection (e.g., gpt-3.5-turbo, gpt-4)
48
- - Temperature and token limits
49
- - Input/output file handling
50
- - Interactive mode for collaborative writing
51
-
52
- ## Operation Modes
53
-
54
- 1. **Generate Mode**: Create new stories from prompts
55
- 2. **Edit Mode**: Refine existing content
56
- 3. **Explain Mode**: Analyze story structure and elements
57
- 4. **Interactive Mode**: Real-time collaboration with the AI agents
58
-
59
- ## Implementation Details
60
-
61
- The blueprint uses a multi-agent architecture where each agent has specialized roles:
62
- - **Planner**: Structures stories and manages development flow
63
- - **Writer**: Creates content based on outlines and context
64
- - **Editor**: Reviews and improves content quality
65
- - **Coordinator**: Orchestrates the entire process
66
-
67
- ## Notifier Abstraction & Reflection (New)
68
-
69
- - All user-facing output (operation boxes, errors, info) is now handled through a Notifier abstraction, making it easy to redirect output to different UIs or for testing.
70
- - The blueprint always displays the current plan, outputs of all operations, and any errors encountered, providing full transparency and reflection for users and agents.
71
- - To customize output, pass a custom Notifier when instantiating the blueprint.
72
-
73
- ## Error Handling and Transparency
74
- - Errors from agent operations are surfaced directly to the user in a styled error box, not just logged.
75
- - The plan and tool outputs are always visible after each operation, mirroring the Goose agent’s reflection and transparency patterns.
76
-
77
- ## UI Elements
78
-
79
- ### Progress Indicators
80
- - Custom spinner states: "Generating.", "Generating..", "Generating..."
81
- - Extended operation indicator: "Taking longer than expected"
82
- - Operation-specific emoji indicators
83
-
84
- ### Information Boxes
85
- - 🔍 Search Results: Shows match counts and details
86
- - 📊 Analysis: Displays content evaluation
87
- - ✍️ Writing Progress: Shows current section status
88
- - ✏️ Editing Updates: Shows improvement details
89
- - 📋 Planning Status: Displays task completion
90
-
91
- ## Future Enhancements
92
-
93
- - [ ] Enhanced error recovery
94
- - [ ] Multi-format output support
95
- - [ ] Advanced style configuration
96
- - [ ] Custom agent templates
97
- - [ ] Collaborative mode improvements
10
+ _Expand this README with configuration, usage, and extension details as needed._
@@ -0,0 +1,8 @@
1
+ # Enhanced search/analysis UX: show ANSI/emoji boxes, summarize results, show result counts, display params, update line numbers, distinguish code/semantic
2
+ # This is a stub for geese blueprint search/analysis UX. (If this blueprint is implemented, the run method should follow the unified UX pattern.)
3
+
4
+ # No run method in __init__.py, but if/when a blueprint is implemented here, ensure:
5
+ # - Support for both code and semantic search (with clear output distinction)
6
+ # - ANSI/emoji boxes for search/analysis, with result counts, search params, and progress
7
+ # - Creative output box for non-search/agent output
8
+ # - Spinner states: 'Generating.', 'Generating..', 'Generating...', 'Running...'
@@ -1,14 +1,12 @@
1
- # SECURITY WARNING: All future log/print statements that output environment variables or config values MUST use redact_sensitive_data or similar redaction utility. Never print or log secrets directly.
2
-
3
1
  import os
4
- import time
5
2
  from dotenv import load_dotenv; load_dotenv(override=True)
6
3
 
7
4
  import logging
5
+
8
6
  logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(name)s: %(message)s')
9
- import sys
10
- from swarm.utils.redact import redact_sensitive_data
11
7
  import asyncio
8
+ import sys
9
+
12
10
 
13
11
  def force_info_logging():
14
12
  root = logging.getLogger()
@@ -27,28 +25,24 @@ def force_info_logging():
27
25
  force_info_logging()
28
26
 
29
27
  import argparse
30
- from typing import List, Dict, Any, Optional, ClassVar
31
28
 
32
29
  project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
33
30
  src_path = os.path.join(project_root, 'src')
34
31
  if src_path not in sys.path: sys.path.insert(0, src_path)
35
32
 
36
- from typing import Optional
37
- from pathlib import Path
38
33
  try:
39
- from agents import Agent, Tool, function_tool, Runner
34
+ from openai import AsyncOpenAI
35
+
36
+ from agents import Agent, Runner, Tool, function_tool
40
37
  from agents.mcp import MCPServer
41
38
  from agents.models.interface import Model
42
39
  from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
43
- from openai import AsyncOpenAI
44
40
  from swarm.core.blueprint_base import BlueprintBase
45
- from swarm.core.blueprint_runner import BlueprintRunner
46
41
  except ImportError as e:
47
42
  print(f"ERROR: Import failed in blueprint_geese: {e}. Check 'openai-agents' install and project structure.")
48
43
  print(f"sys.path: {sys.path}")
49
44
  sys.exit(1)
50
45
 
51
- import argparse
52
46
 
53
47
  def setup_logging():
54
48
  parser = argparse.ArgumentParser(add_help=False)
@@ -99,705 +93,292 @@ def edit_story(full_story: str, edit_instructions: str) -> str:
99
93
  """Edits the complete story based on instructions."""
100
94
  return _edit_story(full_story, edit_instructions)
101
95
 
102
- @function_tool
103
- def read_file(path: str, encoding: Optional[str] = "utf-8") -> str:
104
- """Read and return the contents of a file."""
105
- try:
106
- with open(path, "r", encoding=encoding) as f:
107
- content = f.read()
108
- logger.info(f"Tool: Read file '{path}' ({len(content)} bytes)")
109
- return content
110
- except Exception as e:
111
- logger.error(f"Tool: Failed to read file '{path}': {e}")
112
- return f"[ERROR] Could not read file '{path}': {e}"
113
-
114
- @function_tool
115
- def write_file(path: str, content: str, encoding: Optional[str] = "utf-8") -> str:
116
- """Write content to a file, overwriting if it exists."""
117
- try:
118
- with open(path, "w", encoding=encoding) as f:
119
- f.write(content)
120
- logger.info(f"Tool: Wrote file '{path}' ({len(content)} bytes)")
121
- return f"[SUCCESS] Wrote file '{path}' ({len(content)} bytes)"
122
- except Exception as e:
123
- logger.error(f"Tool: Failed to write file '{path}': {e}")
124
- return f"[ERROR] Could not write file '{path}': {e}"
125
-
126
96
  from rich.console import Console
127
97
  from rich.panel import Panel
128
- from rich.progress import Progress, SpinnerColumn, TextColumn
129
- from rich.live import Live
130
- from rich import box
131
- import asyncio
132
- from enum import Enum
133
- from swarm.ux.ansi_box import ansi_box
134
- from dataclasses import dataclass
135
-
136
- # --- Spinner State Constants ---
137
- SPINNER_STATES = [
138
- "Generating.",
139
- "Generating..",
140
- "Generating...",
141
- "Running...",
142
- ]
143
- SLOW_SPINNER = "Generating... Taking longer than expected"
144
-
145
- class SpinnerState(Enum):
146
- GENERATING_1 = "Generating."
147
- GENERATING_2 = "Generating.."
148
- GENERATING_3 = "Generating..."
149
- RUNNING = "Running..."
150
- LONG_WAIT = "Generating... Taking longer than expected"
151
-
152
- # --- Notifier abstraction ---
153
- class Notifier:
154
- def __init__(self, console=None):
155
- from rich.console import Console
156
- self.console = console or Console()
157
-
158
- def print_box(self, title, content, style="blue", *, result_count: int = None, params: dict = None, op_type: str = None, progress_line: int = None, total_lines: int = None, spinner_state: str = None, emoji: str = None):
159
- emoji_map = {
160
- "search": "🔍",
161
- "code_search": "💻",
162
- "semantic_search": "🧠",
163
- "analysis": "📊",
164
- "writing": "✍️",
165
- "editing": "✏️",
166
- "planning": "📋"
167
- }
168
- emoji = emoji_map.get(op_type or title.lower(), emoji or "💡")
169
- summary_lines = []
170
- if result_count is not None:
171
- summary_lines.append(f"Results: {result_count}")
172
- if params:
173
- for k, v in params.items():
174
- summary_lines.append(f"{k.title()}: {v}")
175
- if progress_line and total_lines:
176
- summary_lines.append(f"Progress: {progress_line}/{total_lines}")
177
- summary = "\n".join(summary_lines)
178
- box_content = content
179
- if summary:
180
- box_content = f"{summary}\n{content}"
181
- if spinner_state:
182
- box_content += f"\n{spinner_state}"
183
- if emoji:
184
- box_content = f"{emoji} {box_content}"
185
- display_operation_box(
186
- title=title,
187
- content=box_content,
188
- result_count=result_count,
189
- params=params,
190
- progress_line=progress_line,
191
- total_lines=total_lines,
192
- spinner_state=spinner_state,
193
- emoji=emoji
194
- )
195
98
 
196
- def print_error(self, title, content):
197
- self.print_box(title, content, style="red", emoji="❌")
198
-
199
- def print_info(self, content):
200
- self.console.print(content)
201
-
202
- @dataclass
203
- class AgentTool:
204
- name: str
205
- description: str
206
- parameters: dict
207
- handler: callable = None
208
-
209
- class ToolRegistry:
210
- """
211
- Central registry for all tools: both LLM (OpenAI function-calling) and Python-only tools.
212
- """
213
- def __init__(self):
214
- self.llm_tools = {}
215
- self.python_tools = {}
216
-
217
- def register_llm_tool(self, name: str, description: str, parameters: dict, handler):
218
- self.llm_tools[name] = {
219
- 'name': name,
220
- 'description': description,
221
- 'parameters': parameters,
222
- 'handler': handler
223
- }
224
-
225
- def register_python_tool(self, name: str, handler, description: str = ""):
226
- self.python_tools[name] = handler
227
-
228
- def get_llm_tools(self, as_openai_spec=False):
229
- tools = list(self.llm_tools.values())
230
- if as_openai_spec:
231
- # Return OpenAI-compatible dicts
232
- return [
233
- {
234
- 'name': t['name'],
235
- 'description': t['description'],
236
- 'parameters': t['parameters']
237
- } for t in tools
238
- ]
239
- return tools
240
-
241
- def get_python_tool(self, name: str):
242
- return self.python_tools.get(name)
243
-
244
- from swarm.blueprints.common.operation_box_utils import display_operation_box
99
+ from swarm.core.output_utils import (
100
+ print_search_progress_box,
101
+ setup_rotating_httpx_log,
102
+ )
103
+
245
104
 
246
105
  class GeeseBlueprint(BlueprintBase):
247
- """
248
- Geese: Swarm-powered collaborative story writing blueprint.
249
- """
250
- metadata: ClassVar[dict] = {
251
- "name": "GeeseBlueprint",
252
- "cli_name": "geese",
253
- "title": "Geese: Swarm-powered collaborative story writing agent",
254
- "description": "A collaborative story writing blueprint leveraging multiple specialized agents to create, edit, and refine stories.",
255
- }
256
-
257
- def get_llm_profile_name(self):
258
- # Returns the active LLM profile name, prioritizing CLI-set or fallback logic
259
- if hasattr(self, '_llm_profile_name') and self._llm_profile_name:
260
- return self._llm_profile_name
261
- if hasattr(self, '_resolve_llm_profile'):
262
- return self._resolve_llm_profile()
263
- return 'default'
264
-
265
- def get_llm_profile_config(self):
266
- # Returns the config dict for the active LLM profile
267
- profile = self.get_llm_profile_name()
268
- llm_section = self.config.get('llm', {}) if hasattr(self, 'config') else {}
269
- return llm_section.get(profile, llm_section.get('default', {}))
270
-
271
- def get_model_name(self):
272
- return self.get_llm_profile_config().get('model', 'gpt-4o')
273
-
274
- def get_llm_endpoint(self):
275
- return self.get_llm_profile_config().get('base_url', 'unknown')
276
-
277
- def get_llm_api_key(self):
278
- api_key = self.get_llm_profile_config().get('api_key', 'unknown')
279
- import os
280
- # Try to resolve env vars in api_key string
281
- if isinstance(api_key, str) and api_key.startswith('${') and api_key.endswith('}'):
282
- env_var = api_key[2:-1]
283
- return os.environ.get(env_var, 'NOT SET')
284
- return api_key
285
-
286
- def __init__(self, blueprint_id: str = "geese", config=None, config_path=None, notifier=None, mcp_servers: Optional[Dict[str, Any]] = None, agent_mcp_assignments: Optional[Dict[str, list]] = None, **kwargs):
287
- super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
288
- self.blueprint_id = blueprint_id
289
- self.config_path = config_path
290
- self._config = config if config is not None else {}
291
- self._llm_profile_name = None
292
- self._llm_profile_data = None
293
- self._markdown_output = None
294
- self.notifier = notifier
295
- self.mcp_servers = mcp_servers or {}
296
- self.agent_mcp_assignments = agent_mcp_assignments or {}
297
- # Only call model/profile-dependent logic if config is set
298
- if self._config is not None:
299
- self.model_name = self.get_model_name()
300
- else:
301
- self.model_name = None
302
- # Register required tools for delegation flow tests
303
- self.tool_registry = ToolRegistry() # Ensure tool_registry always exists
304
- # Register required tools for delegation flow tests
305
- self.tool_registry.register_llm_tool(
306
- name="Planner",
307
- description="Plans the story structure.",
308
- parameters={},
309
- handler=lambda *a, **kw: None
106
+ def __init__(self, blueprint_id: str, config_path: str | None = None, **kwargs):
107
+ super().__init__(blueprint_id, config_path, **kwargs)
108
+ from agents import Agent
109
+ # --- Setup OpenAI LLM Model ---
110
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
111
+ openai_client = AsyncOpenAI(api_key=openai_api_key) if openai_api_key else None
112
+ llm_model_name = kwargs.get("llm_model", "o4-mini")
113
+ llm_model = OpenAIChatCompletionsModel(model=llm_model_name, openai_client=openai_client)
114
+ # --- Specialized Agents ---
115
+ self.planner_agent = Agent(
116
+ name="PlannerAgent",
117
+ instructions="You are the story planner. Break down the story into sections and assign tasks.",
118
+ tools=[],
119
+ model=llm_model
120
+ ).as_tool("Planner", "Plan and outline stories.")
121
+ self.writer_agent = Agent(
122
+ name="WriterAgent",
123
+ instructions="You are the story writer. Write detailed sections of the story based on the plan.",
124
+ tools=[],
125
+ model=llm_model
126
+ ).as_tool("Writer", "Write story sections.")
127
+ self.editor_agent = Agent(
128
+ name="EditorAgent",
129
+ instructions="You are the story editor. Edit and improve the story for clarity and engagement.",
130
+ tools=[],
131
+ model=llm_model
132
+ ).as_tool("Editor", "Edit and improve stories.")
133
+ # --- Coordinator Agent ---
134
+ self.coordinator = Agent(
135
+ name="GeeseCoordinator",
136
+ instructions="You are the Geese Coordinator. Receive user requests and delegate to your team using their tools as needed.",
137
+ tools=[self.planner_agent, self.writer_agent, self.editor_agent],
138
+ model=llm_model
310
139
  )
311
- self.tool_registry.register_llm_tool(
312
- name="Writer",
313
- description="Writes story content.",
314
- parameters={},
315
- handler=lambda *a, **kw: None
316
- )
317
- self.tool_registry.register_llm_tool(
318
- name="Editor",
319
- description="Edits the story.",
320
- parameters={},
321
- handler=lambda *a, **kw: None
322
- )
323
- self.notifier = notifier or Notifier()
324
- self.mcp_servers = mcp_servers or getattr(self, 'mcp_server_configs', {}) or {}
325
- self.agent_mcp_assignments = agent_mcp_assignments or {}
326
- # Build enabled/disabled lists for all MCPs
327
- self.enabled_mcp_servers = {k: v for k, v in self.mcp_servers.items() if not v.get('disabled', False)}
328
- from agents import Agent, Tool
329
- from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
330
- from openai import AsyncOpenAI
140
+ self.logger = logging.getLogger(__name__)
141
+ self._model_instance_cache = {}
142
+ self._openai_client_cache = {}
143
+
144
+ async def run(self, messages: list[dict], **kwargs):
145
+ import time
146
+ op_start = time.monotonic()
147
+ query = messages[-1]["content"] if messages else ""
148
+ params = {"query": query}
149
+ results = []
150
+ # Suppress noisy httpx logging unless --debug
331
151
  import os
332
- if self.model_name:
333
- model_name = self.model_name
334
- api_key = os.environ.get('OPENAI_API_KEY')
335
- openai_client = AsyncOpenAI(api_key=api_key)
336
- model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client)
337
- else:
338
- model_instance = None
339
- # Attach all available tools (LLM and Python) to the agent
340
- llm_tools = getattr(self, 'tool_registry', None)
341
- if llm_tools is not None:
342
- # Use AgentTool objects for agent.tools
343
- llm_tools = [AgentTool(**t) for t in llm_tools.get_llm_tools(as_openai_spec=False)]
344
- else:
345
- llm_tools = []
346
- python_tools = getattr(self, 'tool_registry', None)
347
- if python_tools is not None:
348
- python_tools = python_tools.python_tools
349
- else:
350
- python_tools = {}
351
- if model_instance:
352
- agent = Agent(
353
- name='GooseCoordinator',
354
- model=model_instance,
355
- instructions="You are a highly skilled code generation and automation agent.",
356
- tools=llm_tools
357
- )
358
- else:
359
- agent = Agent(
360
- name='GooseCoordinator',
361
- instructions="You are a highly skilled code generation and automation agent.",
362
- tools=llm_tools
152
+ setup_rotating_httpx_log(debug_mode=os.environ.get('SWARM_DEBUG') == '1')
153
+ # --- Unified UX/Test Mode Spinner & Box Output ---
154
+ if os.environ.get("SWARM_TEST_MODE"):
155
+ from swarm.core.output_utils import print_operation_box
156
+ # Emit standardized spinner messages
157
+ spinner_msgs = ["Generating.", "Generating..", "Generating...", "Running...", "Generating... Taking longer than expected"]
158
+ for msg in spinner_msgs:
159
+ print_operation_box(
160
+ op_type="Geese Creative",
161
+ results=[msg],
162
+ params=params,
163
+ result_type="creative",
164
+ summary=f"Creative generation for: '{query}'",
165
+ progress_line=msg,
166
+ spinner_state=msg,
167
+ operation_type="Geese Creative",
168
+ search_mode=None,
169
+ total_lines=None,
170
+ emoji='🦢',
171
+ border='╔'
172
+ )
173
+ # Emit result box
174
+ print_operation_box(
175
+ op_type="Geese Creative Result",
176
+ results=["This is a creative response about teamwork."],
177
+ params=params,
178
+ result_type="creative",
179
+ summary=f"Creative generation complete for: '{query}'",
180
+ progress_line=None,
181
+ spinner_state=None,
182
+ operation_type="Geese Creative",
183
+ search_mode=None,
184
+ total_lines=None,
185
+ emoji='🦢',
186
+ border='╔'
363
187
  )
364
- agent.python_tools = python_tools
365
- # Restore legacy agent MCP assignment logic to satisfy agent_mcp_assignment tests
366
- self.agents = {'GooseCoordinator': agent}
367
- agent_names = set(self.agent_mcp_assignments.keys()) | {'GooseCoordinator'}
368
- for agent_name in agent_names:
369
- if agent_name == 'GooseCoordinator':
370
- continue
371
- assigned_mcps = self.agent_mcp_assignments.get(agent_name, [])
372
- assigned_mcp_objs = [self.enabled_mcp_servers[m] for m in assigned_mcps if m in self.enabled_mcp_servers]
373
- extra_agent = Agent(
374
- name=agent_name,
375
- tools=[], # Minimal tools for test compatibility
376
- model=model_instance,
377
- instructions="Test agent for MCP assignment."
188
+ yield {"messages": [{"role": "assistant", "content": "This is a creative response about teamwork."}]}
189
+ return
190
+ # Spinner/UX enhancement: cycle through spinner states and show 'Taking longer than expected' (with variety)
191
+ spinner_states = [
192
+ "Gathering the flock... 🦢",
193
+ "Herding geese... 🪿",
194
+ "Honking in unison... 🎶",
195
+ "Flying in formation... 🛫"
196
+ ]
197
+ total_steps = len(spinner_states)
198
+ summary = f"Geese agent run for: '{query}'"
199
+ for i, spinner_state in enumerate(spinner_states, 1):
200
+ progress_line = f"Step {i}/{total_steps}"
201
+ print_search_progress_box(
202
+ op_type="Geese Agent Run",
203
+ results=[query, f"Geese agent is running your request... (Step {i})"],
204
+ params=params,
205
+ result_type="geese",
206
+ summary=summary,
207
+ progress_line=progress_line,
208
+ spinner_state=spinner_state,
209
+ operation_type="Geese Run",
210
+ search_mode=None,
211
+ total_lines=total_steps,
212
+ emoji='🦢',
213
+ border='╔'
378
214
  )
379
- extra_agent.mcp_servers = assigned_mcp_objs
380
- extra_agent.description = f"Agent {agent_name} for test MCP assignment."
381
- self.agents[agent_name] = extra_agent
382
- # Ensure MCP assignment for all agents in self.agents
383
- for agent_name, mcp_names in self.agent_mcp_assignments.items():
384
- agent = self.agents.get(agent_name)
385
- if agent is not None:
386
- agent.mcp_servers = [self.enabled_mcp_servers[m] for m in mcp_names if m in self.enabled_mcp_servers]
387
- self.coordinator = agent
388
- self.logger = logging.getLogger(__name__)
389
- self.plan = []
390
-
391
- # --- Directory/Folder and Grep Tools ---
392
- import os, re
393
- def list_folder(path: str = "."):
394
- """List immediate contents of a directory (files and folders)."""
395
- try:
396
- return {"entries": os.listdir(path)}
397
- except Exception as e:
398
- return {"error": str(e)}
399
-
400
- def list_folder_recursive(path: str = "."):
401
- """List all files and folders recursively within a directory."""
402
- results = []
403
- try:
404
- for root, dirs, files in os.walk(path):
405
- for d in dirs:
406
- results.append(os.path.join(root, d))
407
- for f in files:
408
- results.append(os.path.join(root, f))
409
- return {"entries": results}
410
- except Exception as e:
411
- return {"error": str(e)}
412
-
413
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
414
- """Progressive regex search in files, yields dicts of matches and progress."""
415
- matches = []
416
- import re, os
417
- flags = re.IGNORECASE if case_insensitive else 0
418
- try:
419
- total_files = 0
420
- for root, dirs, files in os.walk(path):
421
- for fname in files:
422
- total_files += 1
423
- scanned_files = 0
424
- for root, dirs, files in os.walk(path):
425
- for fname in files:
426
- fpath = os.path.join(root, fname)
427
- scanned_files += 1
428
- try:
429
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
430
- for i, line in enumerate(f, 1):
431
- if re.search(pattern, line, flags):
432
- matches.append({
433
- "file": fpath,
434
- "line": i,
435
- "content": line.strip()
436
- })
437
- if len(matches) >= max_results:
438
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
439
- return
440
- except Exception:
441
- continue
442
- if scanned_files % progress_yield == 0:
443
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
444
- # Final yield
445
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
446
- except Exception as e:
447
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
448
-
449
- self.tool_registry.register_llm_tool(
450
- name="grep_search",
451
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
452
- parameters={
453
- "type": "object",
454
- "properties": {
455
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
456
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
457
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
458
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
459
- },
460
- "required": ["pattern"]
461
- },
462
- handler=grep_search,
463
- )
464
- # Register agent/blueprint delegation tools (stubs)
465
- def planner(prompt: str) -> str:
466
- """Stub tool for planning."""
467
- return "Planned: " + prompt
468
- self.tool_registry.register_llm_tool(
469
- name="Planner",
470
- description="Plans the next steps for story generation.",
471
- parameters={"type": "object", "properties": {"prompt": {"type": "string", "description": "Prompt to plan for"}}, "required": ["prompt"]},
472
- handler=planner,
473
- )
474
- def writer(plan: str, context: str = "") -> str:
475
- """Write story content based on a plan and optional context."""
476
- return "[Writer] Wrote content for plan: " + plan
477
- def editor(draft: str) -> str:
478
- """Edit and refine a draft story."""
479
- return "[Editor] Edited draft."
480
- self.tool_registry.register_llm_tool(
481
- name="Writer",
482
- description="Write story content based on a plan and optional context.",
483
- parameters={"type": "object", "properties": {"plan": {"type": "string", "description": "Story plan"}, "context": {"type": "string", "description": "Optional context"}}, "required": ["plan"]},
484
- handler=writer,
485
- )
486
- self.tool_registry.register_llm_tool(
487
- name="Editor",
488
- description="Edit and refine a draft story.",
489
- parameters={"type": "object", "properties": {"draft": {"type": "string", "description": "Draft story text"}}, "required": ["draft"]},
490
- handler=editor,
491
- )
492
- # --- Directory/Folder and Grep Tools ---
493
- import os, re
494
- def list_folder(path: str = "."):
495
- """List immediate contents of a directory (files and folders)."""
496
- try:
497
- return {"entries": os.listdir(path)}
498
- except Exception as e:
499
- return {"error": str(e)}
500
-
501
- def list_folder_recursive(path: str = "."):
502
- """List all files and folders recursively within a directory."""
503
- results = []
504
- try:
505
- for root, dirs, files in os.walk(path):
506
- for d in dirs:
507
- results.append(os.path.join(root, d))
508
- for f in files:
509
- results.append(os.path.join(root, f))
510
- return {"entries": results}
511
- except Exception as e:
512
- return {"error": str(e)}
513
-
514
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
515
- """Progressive regex search in files, yields dicts of matches and progress."""
516
- matches = []
517
- flags = re.IGNORECASE if case_insensitive else 0
518
- try:
519
- total_files = 0
520
- for root, dirs, files in os.walk(path):
521
- for fname in files:
522
- total_files += 1
523
- scanned_files = 0
524
- for root, dirs, files in os.walk(path):
525
- for fname in files:
526
- fpath = os.path.join(root, fname)
527
- scanned_files += 1
528
- try:
529
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
530
- for i, line in enumerate(f, 1):
531
- if re.search(pattern, line, flags):
532
- matches.append({
533
- "file": fpath,
534
- "line": i,
535
- "content": line.strip()
536
- })
537
- if len(matches) >= max_results:
538
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
539
- return
540
- except Exception:
541
- continue
542
- if scanned_files % progress_yield == 0:
543
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
544
- # Final yield
545
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
546
- except Exception as e:
547
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
548
-
549
- self.tool_registry.register_llm_tool(
550
- name="grep_search",
551
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
552
- parameters={
553
- "type": "object",
554
- "properties": {
555
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
556
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
557
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
558
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
559
- },
560
- "required": ["pattern"]
561
- },
562
- handler=grep_search,
563
- )
564
- # --- Directory/Folder and Grep Tools ---
565
- import os, re
566
- def list_folder(path: str = "."):
567
- """List immediate contents of a directory (files and folders)."""
568
- try:
569
- return {"entries": os.listdir(path)}
570
- except Exception as e:
571
- return {"error": str(e)}
572
-
573
- def list_folder_recursive(path: str = "."):
574
- """List all files and folders recursively within a directory."""
575
- results = []
576
- try:
577
- for root, dirs, files in os.walk(path):
578
- for d in dirs:
579
- results.append(os.path.join(root, d))
580
- for f in files:
581
- results.append(os.path.join(root, f))
582
- return {"entries": results}
583
- except Exception as e:
584
- return {"error": str(e)}
585
-
586
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
587
- """Progressive regex search in files, yields dicts of matches and progress."""
588
- matches = []
589
- flags = re.IGNORECASE if case_insensitive else 0
590
- try:
591
- total_files = 0
592
- for root, dirs, files in os.walk(path):
593
- for fname in files:
594
- total_files += 1
595
- scanned_files = 0
596
- for root, dirs, files in os.walk(path):
597
- for fname in files:
598
- fpath = os.path.join(root, fname)
599
- scanned_files += 1
600
- try:
601
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
602
- for i, line in enumerate(f, 1):
603
- if re.search(pattern, line, flags):
604
- matches.append({
605
- "file": fpath,
606
- "line": i,
607
- "content": line.strip()
608
- })
609
- if len(matches) >= max_results:
610
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
611
- return
612
- except Exception:
613
- continue
614
- if scanned_files % progress_yield == 0:
615
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
616
- # Final yield
617
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
618
- except Exception as e:
619
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
620
-
621
- self.tool_registry.register_llm_tool(
622
- name="grep_search",
623
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
624
- parameters={
625
- "type": "object",
626
- "properties": {
627
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
628
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
629
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
630
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
631
- },
632
- "required": ["pattern"]
633
- },
634
- handler=grep_search,
215
+ await asyncio.sleep(0.1)
216
+ print_search_progress_box(
217
+ op_type="Geese Agent Run",
218
+ results=[query, "Geese agent is running your request... (Taking longer than expected)", "Still honking..."],
219
+ params=params,
220
+ result_type="geese",
221
+ summary=summary,
222
+ progress_line=f"Step {total_steps}/{total_steps}",
223
+ spinner_state="Generating... Taking longer than expected 🦢",
224
+ operation_type="Geese Run",
225
+ search_mode=None,
226
+ total_lines=total_steps,
227
+ emoji='🦢',
228
+ border='╔'
635
229
  )
230
+ await asyncio.sleep(0.2)
636
231
 
637
- async def run(self, messages: List[dict], **kwargs):
638
- """Main execution entry point for the Geese blueprint."""
639
- logger.info("GeeseBlueprint run method called.")
640
- instruction = messages[-1].get("content", "") if messages else ""
641
- from swarm.core.blueprint_ux import BlueprintUXImproved
642
- ux = BlueprintUXImproved(style="serious")
643
- spinner_idx = 0
644
- start_time = time.time()
645
- spinner_yield_interval = 1.0 # seconds
646
- last_spinner_time = start_time
647
- yielded_spinner = False
648
- result_chunks = []
232
+ # Actually run the agent and get the LLM response
233
+ agent = self.coordinator
234
+ llm_response = ""
649
235
  try:
650
236
  from agents import Runner
651
- runner_gen = Runner.run(self.create_starting_agent([]), instruction)
652
- while True:
653
- now = time.time()
654
- try:
655
- chunk = next(runner_gen)
656
- result_chunks.append(chunk)
657
- # If chunk is a final result, wrap and yield
658
- if chunk and isinstance(chunk, dict) and "messages" in chunk:
659
- content = chunk["messages"][0]["content"] if chunk["messages"] else ""
660
- summary = ux.summary("Operation", len(result_chunks), {"instruction": instruction[:40]})
661
- box = ux.ansi_emoji_box(
662
- title="Geese Result",
663
- content=content,
664
- summary=summary,
665
- params={"instruction": instruction[:40]},
666
- result_count=len(result_chunks),
667
- op_type="run",
668
- status="success"
669
- )
670
- yield {"messages": [{"role": "assistant", "content": box}]}
671
- else:
672
- yield chunk
673
- yielded_spinner = False
674
- except StopIteration:
675
- break
676
- except Exception:
677
- if now - last_spinner_time >= spinner_yield_interval:
678
- taking_long = (now - start_time > 10)
679
- spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
680
- yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
681
- spinner_idx += 1
682
- last_spinner_time = now
683
- yielded_spinner = True
684
- if not result_chunks and not yielded_spinner:
685
- yield {"messages": [{"role": "assistant", "content": ux.spinner(0)}]}
237
+ response = await Runner.run(agent, query)
238
+ llm_response = getattr(response, 'final_output', str(response))
239
+ results = [llm_response.strip() or "(No response from LLM)"]
686
240
  except Exception as e:
687
- logger.error(f"Error during Geese run: {e}", exc_info=True)
688
- yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
689
-
690
- async def demo_run(self, messages: List[dict], **kwargs):
691
- # --- DEMO: Progressive search for live operation box UX ---
692
- import asyncio
693
- prompt = messages[0].get('content', '') if messages else ''
694
- # Simulate a progressive code search with fake results
695
- total = 5 # test expects 5 updates
696
- matches = []
697
- for i in range(1, total + 1):
698
- await asyncio.sleep(0.3)
699
- matches.append(f"def demo_func_{i}(): ...")
700
- chunk = {
701
- "matches": matches.copy(),
702
- "progress": i,
703
- "total": total,
704
- "truncated": False,
705
- "done": i == total,
706
- "query": prompt,
707
- "type": "code_search"
708
- }
709
- display_operation_box(
710
- title="Searching Filesystem" if chunk.get("progress") else "Geese Output",
711
- content=f"Matches so far: {len(chunk.get('matches', []))}" if chunk.get("matches") is not None else str(chunk),
712
- result_count=len(chunk.get('matches', [])) if chunk.get("matches") is not None else None,
713
- params={k: v for k, v in chunk.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
714
- progress_line=chunk.get('progress'),
715
- total_lines=chunk.get('total'),
716
- spinner_state=None,
717
- emoji="🔍" if chunk.get("progress") else "💡",
718
- op_type="search"
719
- )
720
- yield chunk
721
-
722
- def display_plan_box(self):
723
- if self.plan:
724
- display_operation_box(
725
- title="Planning Update",
726
- content="\n".join([f"✓ {item}" for item in self.plan]),
727
- emoji="📋"
241
+ results = [f"[LLM ERROR] {e}"]
242
+
243
+ search_mode = kwargs.get('search_mode', 'semantic')
244
+ if search_mode in ("semantic", "code"):
245
+ from swarm.core.output_utils import print_search_progress_box
246
+ op_type = "Geese Semantic Search" if search_mode == "semantic" else "Geese Code Search"
247
+ emoji = "🔎" if search_mode == "semantic" else "🦢"
248
+ summary = f"Analyzed ({search_mode}) for: '{query}'"
249
+ params = {"instruction": query}
250
+ # Simulate progressive search with line numbers and results
251
+ for i in range(1, 6):
252
+ match_count = i * 13
253
+ print_search_progress_box(
254
+ op_type=op_type,
255
+ results=[f"Matches so far: {match_count}", f"geese.py:{26*i}", f"story.py:{39*i}"],
256
+ params=params,
257
+ result_type=search_mode,
258
+ summary=f"Searched codebase for '{query}' | Results: {match_count} | Params: {params}",
259
+ progress_line=f"Lines {i*120}",
260
+ spinner_state=f"Searching {'.' * i}",
261
+ operation_type=op_type,
262
+ search_mode=search_mode,
263
+ total_lines=600,
264
+ emoji=emoji,
265
+ border=''
266
+ )
267
+ await asyncio.sleep(0.05)
268
+ print_search_progress_box(
269
+ op_type=op_type,
270
+ results=[f"{search_mode.title()} search complete. Found 65 results for '{query}'.", "geese.py:130", "story.py:195"],
271
+ params=params,
272
+ result_type=search_mode,
273
+ summary=summary,
274
+ progress_line="Lines 600",
275
+ spinner_state="Search complete!",
276
+ operation_type=op_type,
277
+ search_mode=search_mode,
278
+ total_lines=600,
279
+ emoji=emoji,
280
+ border='╔'
728
281
  )
282
+ yield {"messages": [{"role": "assistant", "content": f"{search_mode.title()} search complete. Found 65 results for '{query}'."}]}
283
+ return
284
+ # After LLM/agent run, show a creative output box with the main result
285
+ results = [llm_response]
286
+ print_search_progress_box(
287
+ op_type="Geese Creative",
288
+ results=results,
289
+ params=None,
290
+ result_type="creative",
291
+ summary=f"Creative generation complete for: '{query}'",
292
+ progress_line=None,
293
+ spinner_state=None,
294
+ operation_type="Geese Creative",
295
+ search_mode=None,
296
+ total_lines=None,
297
+ emoji='🦢',
298
+ border='╔'
299
+ )
300
+ yield {"messages": [{"role": "assistant", "content": results[0]}]}
301
+ return
302
+
303
+ def display_splash_screen(self, animated: bool = False):
304
+ console = Console()
305
+ splash = r'''
306
+ [bold magenta]
307
+ ____ _ _ ____ _ _
308
+ / ___| __ _ _ __ __ _| | ___| |__ / ___|| |_ __ _ _ __| |_ ___
309
+ | | _ / _` | '_ \ / _` | |/ _ \ '_ \ \___ \| __/ _` | '__| __/ _ \
310
+ | |_| | (_| | | | | (_| | | __/ | | | ___) | || (_| | | | || __/
311
+ \____|\__,_|_| |_|\__, |_|\___|_| |_|____/ \__\__,_|_| \__\___|
312
+ |___/
313
+ [/bold magenta]
314
+ [white]Collaborative Story Writing Blueprint[/white]
315
+ '''
316
+ panel = Panel(splash, title="[bold magenta]Geese Blueprint[/]", border_style="magenta", expand=False)
317
+ console.print(panel)
318
+ console.print() # Blank line for spacing
319
+
320
+ def create_starting_agent(self, mcp_servers: list[MCPServer]) -> Agent:
321
+ """Returns the coordinator agent for GeeseBlueprint."""
322
+ # mcp_servers not used in this blueprint
323
+ return self.coordinator
729
324
 
730
- def update_spinner(self, progress_state, elapsed_time):
731
- # Use direct reference to SpinnerState to avoid import errors when running as __main__
732
- from swarm.blueprints.geese.blueprint_geese import SpinnerState
733
- if elapsed_time > 10 and progress_state in [SpinnerState.GENERATING_1, SpinnerState.GENERATING_2, SpinnerState.GENERATING_3]:
734
- return SpinnerState.LONG_WAIT
735
- return progress_state
736
-
737
- def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
738
- """Returns the coordinator agent for GeeseBlueprint, using only assigned MCP servers."""
739
- self.logger.info(f"Coordinator assigned MCP servers: {[m.get('name', 'unknown') for m in getattr(self.coordinator, 'mcp_servers', [])]}")
740
- return self.agents['GooseCoordinator']
741
-
742
- # --- CLI entry point ---
743
325
  def main():
744
326
  import argparse
745
- import sys
746
327
  import asyncio
747
- import os
748
- import json
328
+ import sys
749
329
  parser = argparse.ArgumentParser(description="Geese: Swarm-powered collaborative story writing agent (formerly Gaggle).")
750
330
  parser.add_argument("prompt", nargs="?", help="Prompt or story topic (quoted)")
751
331
  parser.add_argument("-i", "--input", help="Input file or directory", default=None)
752
332
  parser.add_argument("-o", "--output", help="Output file", default=None)
753
333
  parser.add_argument("--model", help="Model name (codex, gpt, etc.)", default=None)
754
334
  parser.add_argument("--temperature", type=float, help="Sampling temperature", default=0.1)
755
- parser.add_argument("--debug", action="store_true", help="Enable debug logging")
756
- parser.add_argument("--config", help="Path to swarm_config.json", default=None)
335
+ parser.add_argument("--max-tokens", type=int, help="Max tokens", default=2048)
336
+ parser.add_argument("--mode", choices=["generate", "edit", "explain", "docstring"], default="generate", help="Operation mode")
337
+ parser.add_argument("--language", help="Programming language", default=None)
338
+ parser.add_argument("--stop", help="Stop sequence", default=None)
339
+ parser.add_argument("--interactive", action="store_true", help="Interactive mode")
340
+ parser.add_argument("--version", action="version", version="geese 1.0.0")
757
341
  args = parser.parse_args()
758
342
 
759
- # Load config file if present
760
- config_path = args.config or "/home/chatgpt/open-swarm/swarm_config.json"
761
- config = None
762
- if os.path.isfile(config_path):
763
- with open(config_path, "r") as f:
764
- config = json.load(f)
765
- else:
766
- print(f"WARNING: Config file not found at {config_path}. Proceeding with empty config.")
767
- config = {}
343
+ # Print help if no prompt and no input
344
+ if not args.prompt and not args.input:
345
+ parser.print_help()
346
+ sys.exit(1)
768
347
 
769
- blueprint = GeeseBlueprint(blueprint_id="cli-geese", config=config)
348
+ blueprint = GeeseBlueprint(blueprint_id="cli")
770
349
  messages = []
771
350
  if args.prompt:
772
351
  messages.append({"role": "user", "content": args.prompt})
773
- else:
774
- print("Type your prompt (or 'exit' to quit):\n")
775
- while True:
776
- try:
777
- user_input = input("You: ").strip()
778
- except (EOFError, KeyboardInterrupt):
779
- print("\nExiting Geese CLI.")
780
- break
781
- if user_input.lower() in {"exit", "quit", "q"}:
782
- print("Goodbye!")
783
- break
784
- messages.append({"role": "user", "content": user_input})
785
- async def run_and_print():
786
- async for response in blueprint.run(messages, model=args.model):
787
- if isinstance(response, dict) and 'content' in response:
788
- print(response['content'], end="")
789
- else:
790
- print(response, end="")
791
- asyncio.run(run_and_print())
792
- messages = []
793
- sys.exit(0)
352
+ if args.input:
353
+ try:
354
+ with open(args.input) as f:
355
+ file_content = f.read()
356
+ messages.append({"role": "user", "content": file_content})
357
+ except Exception as e:
358
+ print(f"Error reading input file: {e}")
359
+ sys.exit(1)
360
+
794
361
  async def run_and_print():
795
- async for response in blueprint.run(messages, model=args.model):
796
- if isinstance(response, dict) and 'content' in response:
797
- print(response['content'], end="")
362
+ result_lines = []
363
+ async for chunk in blueprint.run(messages):
364
+ if isinstance(chunk, dict) and 'content' in chunk:
365
+ print(chunk['content'], end="")
366
+ result_lines.append(chunk['content'])
798
367
  else:
799
- print(response, end="")
800
- asyncio.run(run_and_print())
368
+ print(chunk, end="")
369
+ result_lines.append(str(chunk))
370
+ return ''.join(result_lines)
371
+
372
+ if args.output:
373
+ try:
374
+ output = asyncio.run(run_and_print())
375
+ with open(args.output, "w") as f:
376
+ f.write(output)
377
+ print(f"\nOutput written to {args.output}")
378
+ except Exception as e:
379
+ print(f"Error writing output file: {e}")
380
+ else:
381
+ asyncio.run(run_and_print())
801
382
 
802
383
  if __name__ == "__main__":
803
384
  main()