#!/usr/bin/env python3
import os
import argparse
import json
import subprocess
from openai import OpenAI
import sys
import shlex
# ANSI escape codes for colors
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
RESET = '\033[0m'
# Initialize the OpenAI client
# It will automatically pick up the OPENAI_API_KEY from your environment variables.
try:
client = OpenAI()
except ImportError:
print(f"{Colors.RED}OpenAI Python package not found. Please install it with 'pip install openai'{Colors.RESET}")
exit(1)
except Exception as e:
print(f"{Colors.RED}Error initializing OpenAI client: {e}{Colors.RESET}")
print(f"{Colors.YELLOW}Please ensure your OPENAI_API_KEY environment variable is set correctly.{Colors.RESET}")
exit(1)
class AutonomousBot:
"""
An autonomous bot that executes tasks based on a user prompt.
It can call functions, manage its state, and decompose complex tasks.
"""
def __init__(self, prompt, memory=None):
"""
Initializes the bot with a prompt and an optional memory of past actions.
"""
self.initial_prompt = prompt
self.memory = memory if memory is not None else []
self.is_finished = False
self.tools = [
{
"type": "function",
"function": {
"name": "create_file",
"description": "Create a new file with specified content. This is for application code. Overwrites if the file exists.",
"parameters": {
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "The full path for the new file."},
"content": {"type": "string", "description": "The full, complete content to write into the file."}
},
"required": ["filepath", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "create_test_file",
"description": "Create a new test file with specified content to verify the application code.",
"parameters": {
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "The full path for the new test file (e.g., 'test_app.py')."},
"content": {"type": "string", "description": "The full, complete content for the test file."}
},
"required": ["filepath", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the content of a file.",
"parameters": {
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "The full path of the file to read."}
},
"required": ["filepath"]
}
}
},
{
"type": "function",
"function": {
"name": "delete_file",
"description": "Delete a file.",
"parameters": {
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "The full path of the file to delete."}
},
"required": ["filepath"]
}
}
},
{
"type": "function",
"function": {
"name": "terminal_execute",
"description": "Execute a shell command in the terminal. Use this for tasks like creating directories, listing files, running tests, and installing packages.",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "The command to execute."}
},
"required": ["command"]
}
}
},
{
"type": "function",
"function": {
"name": "finish_task",
"description": "Call this function when the current task is fully completed, verified, and all tests are passing.",
"parameters": {
"type": "object",
"properties": {
"reason": {"type": "string", "description": "A brief summary of why the task is considered finished."}
},
"required": ["reason"]
}
}
},
{
"type": "function",
"function": {
"name": "split_and_delegate",
"description": "If the prompt contains more than one distinct task, use this function to handle the first task and delegate the rest.",
"parameters": {
"type": "object",
"properties": {
"current_task_prompt": {"type": "string", "description": "The prompt for the immediate task this bot instance will handle."},
"remaining_prompt": {"type": "string", "description": "The prompt describing all subsequent tasks."}
},
"required": ["current_task_prompt", "remaining_prompt"]
}
}
}
]
self.available_functions = {
"create_file": self.create_file,
"create_test_file": self.create_test_file,
"read_file": self.read_file,
"delete_file": self.delete_file,
"terminal_execute": self.terminal_execute,
"finish_task": self.finish_task,
"split_and_delegate": self.split_and_delegate,
}
def get_system_prompt(self):
"""Constructs the system prompt for the AI model."""
return f"""You are an autonomous AI builder bot. Your goal is to accomplish the user's request by calling functions to interact with the system.
You have complete CRUD access to the filesystem and can execute terminal commands. Be aware of your actions and their consequences.
Current Directory: {os.getcwd()}
The user's high-level goal is: "{self.initial_prompt}"
Here is a summary of the steps taken so far:
{json.dumps(self.memory, indent=2) if self.memory else 'No actions taken yet.'}
**NEW, STRICT RULES:**
1. **NO PLACEHOLDERS:** All code you generate must be a full, working implementation. Do not generate placeholders, stubs, or incomplete functions.
2. **TEST-DRIVEN DEVELOPMENT:** For any application code you write, you MUST also write a corresponding test file.
3. **EXECUTE TESTS:** After creating the code and the test file, you MUST execute the tests using `terminal_execute` (e.g., `python -m pytest test_file.py`).
4. **VALIDATE & DEBUG:** Analyze the `stdout` and `stderr` from the test execution. If there are any failures, you MUST debug the issue. This means re-reading the code, fixing it with `create_file`, and then re-running the tests. This loop (fix -> re-test) must continue until all tests pass.
5. **SPECIFY VERSIONS:** When installing packages (e.g., using pip), you MUST specify an exact version. For example: `pip install pytest==8.1.1`. Do not use commands like `pip install pytest`.
6. **TASK DECOMPOSITION:** If the prompt contains multiple distinct tasks (e.g., "create an API and then build a frontend for it"), you MUST use the `split_and_delegate` function first.
7. **FINISH CONDITION:** Only call `finish_task` when the entire requested functionality is implemented AND all corresponding tests are passing.
8. **AUTONOMY:** Do not ask for permission. Execute the plan.
"""
def run(self):
"""
Main execution loop for the bot.
"""
print(f"{Colors.GREEN}▶ Starting bot with prompt: '{self.initial_prompt}'{Colors.RESET}")
messages = [
{"role": "system", "content": self.get_system_prompt()},
{"role": "user", "content": self.initial_prompt}
]
while not self.is_finished:
try:
print(f"\n{Colors.BLUE}🤔 Thinking...{Colors.RESET}")
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=messages,
tools=self.tools,
tool_choice="auto",
)
response_message = response.choices[0].message
messages.append(response_message)
tool_calls = response_message.tool_calls
if not tool_calls:
print(f"\n{Colors.CYAN}🤖 AI Response (no tool call):{Colors.RESET}")
no_tool_call_message = response_message.content or "No text content in response."
print(no_tool_call_message)
messages.append({
"role": "user",
"content": "You did not call a function. Please proceed with the next action by calling a function, or call `finish_task` if the entire goal is complete."
})
continue
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = self.available_functions.get(function_name)
function_args = {}
if not function_to_call:
print(f"{Colors.RED}❌ Unknown function called by model: {function_name}{Colors.RESET}")
tool_result = {"status": "error", "message": f"Unknown function: {function_name}"}
else:
try:
function_args = json.loads(tool_call.function.arguments)
print(f"\n{Colors.GREEN}▶ Calling function: `{function_name}` with arguments:{Colors.RESET}")
print(json.dumps(function_args, indent=2))
tool_result = function_to_call(**function_args)
except Exception as e:
print(f"{Colors.RED}❌ Error calling function '{function_name}': {e}{Colors.RESET}")
tool_result = {"status": "error", "message": str(e)}
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": json.dumps(tool_result),
})
self.memory.append({
"function_name": function_name,
"arguments": function_args,
"result": tool_result
})
except Exception as e:
print(f"\n{Colors.RED}❌ An unexpected error occurred in the main loop: {e}{Colors.RESET}")
break
def _write_file(self, filepath, content, purpose=""):
try:
dir_path = os.path.dirname(filepath)
if dir_path:
os.makedirs(dir_path, exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
purpose_text = f" ({purpose})" if purpose else ""
print(f"{Colors.GREEN}✅ File '{filepath}' created{purpose_text} successfully.{Colors.RESET}")
return {"status": "success", "filepath": filepath}
except Exception as e:
print(f"{Colors.RED}❌ Error creating file '{filepath}': {e}{Colors.RESET}")
return {"status": "error", "message": str(e)}
def create_file(self, filepath, content):
return self._write_file(filepath, content, "application code")
def create_test_file(self, filepath, content):
return self._write_file(filepath, content, "test code")
def read_file(self, filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
print(f"{Colors.GREEN}✅ File '{filepath}' read successfully.{Colors.RESET}")
return {"status": "success", "content": content}
except FileNotFoundError:
print(f"{Colors.RED}❌ File not found: '{filepath}'{Colors.RESET}")
return {"status": "error", "message": "File not found."}
except Exception as e:
print(f"{Colors.RED}❌ Error reading file '{filepath}': {e}{Colors.RESET}")
return {"status": "error", "message": str(e)}
def delete_file(self, filepath):
try:
os.remove(filepath)
print(f"{Colors.GREEN}✅ File '{filepath}' deleted successfully.{Colors.RESET}")
return {"status": "success", "filepath": filepath}
except FileNotFoundError:
print(f"{Colors.RED}❌ File not found: '{filepath}'{Colors.RESET}")
return {"status": "error", "message": "File not found."}
except Exception as e:
print(f"{Colors.RED}❌ Error deleting file '{filepath}': {e}{Colors.RESET}")
return {"status": "error", "message": str(e)}
def terminal_execute(self, command):
print(f"Executing command: `{command}`")
try:
command_parts = shlex.split(command)
result = subprocess.run(command_parts, capture_output=True, text=True, check=False)
output = {
"status": "success" if result.returncode == 0 else "error",
"stdout": result.stdout.strip(),
"stderr": result.stderr.strip(),
"returncode": result.returncode
}
print(f" - STDOUT: {result.stdout.strip()}")
if result.stderr.strip():
print(f" - {Colors.YELLOW}STDERR: {result.stderr.strip()}{Colors.RESET}")
print(f" - Return Code: {result.returncode}")
return output
except Exception as e:
print(f"{Colors.RED}❌ Error executing command '{command}': {e}{Colors.RESET}")
return {"status": "error", "message": str(e), "stdout": "", "stderr": str(e), "returncode": -1}
def finish_task(self, reason):
print(f"\n{Colors.YELLOW}🏁 BOT INSTANCE FINISHED: {reason}{Colors.RESET}")
self.is_finished = True
return {"status": "finished"}
def split_and_delegate(self, current_task_prompt, remaining_prompt):
print("\n" + "="*50)
print(" PYRAMID OF BUILDERS")
print("="*50)
print(f"{Colors.MAGENTA}🚀 Delegating remaining prompt to a new bot instance...{Colors.RESET}")
print(f" python {__file__} \"{remaining_prompt}\"")
try:
subprocess.Popen([sys.executable, __file__, remaining_prompt])
print(f"{Colors.GREEN}✅ New bot instance launched successfully.{Colors.RESET}")
except Exception as e:
print(f"{Colors.RED}❌ Failed to launch new bot instance: {e}{Colors.RESET}")
return {"status": "error", "message": f"Failed to delegate: {e}"}
self.initial_prompt = current_task_prompt
print(f"This instance will now proceed to solve its narrowed task: '{current_task_prompt}'")
print("="*50 + "\n")
return {"status": "success", "message": "Task split. This bot will now handle the first part."}
def main():
"""
Entry point for the CLI application.
"""
parser = argparse.ArgumentParser(description="An autonomous bot to execute tasks.")
parser.add_argument("prompt", type=str, help="The user prompt describing the task to be done.")
args = parser.parse_args()
bot = AutonomousBot(args.prompt)
bot.run()
if __name__ == "__main__":
main()