Delete code_jade
Browse files- code_jade/config.json +0 -6
- code_jade/core.py +0 -223
- code_jade/main.py +0 -40
- code_jade/requirements.txt +0 -1
- code_jade/reviewer.py +0 -63
- code_jade/tools.py +0 -127
code_jade/config.json
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"groq_model": "moonshotai/kimi-k2-instruct-0905",
|
| 3 |
-
"max_context": 20,
|
| 4 |
-
"safe_mode": false,
|
| 5 |
-
"work_dir": "./workspace"
|
| 6 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code_jade/core.py
DELETED
|
@@ -1,223 +0,0 @@
|
|
| 1 |
-
import json
|
| 2 |
-
import logging
|
| 3 |
-
from groq import Groq
|
| 4 |
-
from .tools import ToolManager
|
| 5 |
-
from .reviewer import CodeReviewer
|
| 6 |
-
|
| 7 |
-
class CodeJadeAgent:
|
| 8 |
-
def __init__(self, config_path="code_jade/config.json"):
|
| 9 |
-
# Carrega Configuração
|
| 10 |
-
try:
|
| 11 |
-
with open(config_path) as f:
|
| 12 |
-
self.cfg = json.load(f)
|
| 13 |
-
except FileNotFoundError:
|
| 14 |
-
# Fallback se não achar, mas idealmente deve existir
|
| 15 |
-
self.cfg = {"groq_model": "moonshotai/kimi-k2-instruct-0905", "safe_mode": True, "work_dir": "./workspace", "max_context": 20}
|
| 16 |
-
|
| 17 |
-
self.client = Groq(api_key=self._get_api_key())
|
| 18 |
-
self.tools = ToolManager(safe_mode=self.cfg.get("safe_mode", True), work_dir=self.cfg.get("work_dir", "."))
|
| 19 |
-
self.reviewer = CodeReviewer(self.cfg)
|
| 20 |
-
|
| 21 |
-
# System Prompt focado em Code Assistant
|
| 22 |
-
self.system_prompt = """Você é CodeJade, um assistente de programação avançado (estilo Cursor AI).
|
| 23 |
-
Seu objetivo é ajudar o usuário a escrever código, corrigir bugs e explorar o projeto.
|
| 24 |
-
|
| 25 |
-
FERRAMENTAS DISPONÍVEIS:
|
| 26 |
-
Você tem acesso a ferramentas. Para usá-las, você DEVE responder APENAS com um bloco JSON estrito no seguinte formato:
|
| 27 |
-
{"tool": "nome_da_ferramenta", "args": {"arg1": "valor1"}}
|
| 28 |
-
|
| 29 |
-
As ferramentas são:
|
| 30 |
-
1. execute_shell(command: str) -> Executa comandos bash (ls, pip, git, etc).
|
| 31 |
-
2. read_file(filepath: str) -> Lê o conteúdo de um arquivo.
|
| 32 |
-
3. write_file(filepath: str, content: str) -> Cria ou sobrescreve um arquivo.
|
| 33 |
-
4. list_files(path: str) -> Lista arquivos.
|
| 34 |
-
5. run_python(code: str) -> Executa script Python.
|
| 35 |
-
|
| 36 |
-
REGRAS:
|
| 37 |
-
- Se precisar de informações, use 'list_files' ou 'read_file'.
|
| 38 |
-
- Se precisar rodar algo, use 'execute_shell' ou 'run_python'.
|
| 39 |
-
- Se for apenas conversar ou explicar, responda em texto normal (sem JSON).
|
| 40 |
-
- Mantenha respostas diretas e técnicas.
|
| 41 |
-
- Sempre verifique se o código funciona rodando-o se possível.
|
| 42 |
-
|
| 43 |
-
EXEMPLO DE USO:
|
| 44 |
-
Usuário: "Crie um hello world em python"
|
| 45 |
-
CodeJade: {"tool": "write_file", "args": {"filepath": "hello.py", "content": "print('Hello World')"}}
|
| 46 |
-
(Sistema executa e retorna sucesso)
|
| 47 |
-
CodeJade: "Arquivo criado. Quer que eu execute?"
|
| 48 |
-
"""
|
| 49 |
-
self.history = [{"role": "system", "content": self.system_prompt}]
|
| 50 |
-
|
| 51 |
-
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 52 |
-
|
| 53 |
-
def _get_api_key(self):
|
| 54 |
-
import os
|
| 55 |
-
key = os.getenv("GROQ_API_KEY")
|
| 56 |
-
if not key:
|
| 57 |
-
# Tenta pegar do colab se estiver lá
|
| 58 |
-
try:
|
| 59 |
-
from google.colab import userdata
|
| 60 |
-
key = userdata.get('GROQ_API_KEY')
|
| 61 |
-
except:
|
| 62 |
-
pass
|
| 63 |
-
if not key:
|
| 64 |
-
print("⚠️ AVISO: GROQ_API_KEY não encontrada. O agente pode falhar.")
|
| 65 |
-
return "dummy_key"
|
| 66 |
-
return key
|
| 67 |
-
|
| 68 |
-
def _chat(self, messages):
|
| 69 |
-
"""Chama o modelo Groq."""
|
| 70 |
-
try:
|
| 71 |
-
completion = self.client.chat.completions.create(
|
| 72 |
-
messages=messages,
|
| 73 |
-
model=self.cfg["groq_model"],
|
| 74 |
-
temperature=0.3, # Baixa temperatura para código preciso
|
| 75 |
-
stop=None
|
| 76 |
-
)
|
| 77 |
-
return completion.choices[0].message.content
|
| 78 |
-
except Exception as e:
|
| 79 |
-
return f"❌ Erro na API Groq: {e}"
|
| 80 |
-
|
| 81 |
-
def process_tool_call(self, response_text):
|
| 82 |
-
"""Tenta parsear JSON para chamada de ferramenta usando Regex."""
|
| 83 |
-
import json
|
| 84 |
-
import re
|
| 85 |
-
|
| 86 |
-
try:
|
| 87 |
-
# Procura pelo padrão JSON: { ... }
|
| 88 |
-
# O regex considera chaves aninhadas simples, mas foca no bloco principal
|
| 89 |
-
# A opção re.DOTALL permite que o ponto (.) case com novas linhas
|
| 90 |
-
match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 91 |
-
if not match:
|
| 92 |
-
return None
|
| 93 |
-
|
| 94 |
-
json_str = match.group(0)
|
| 95 |
-
|
| 96 |
-
# Tenta carregar o JSON
|
| 97 |
-
data = json.loads(json_str)
|
| 98 |
-
|
| 99 |
-
if "tool" in data and "args" in data:
|
| 100 |
-
return data
|
| 101 |
-
return None
|
| 102 |
-
except json.JSONDecodeError:
|
| 103 |
-
# Fallback: tentar limpar crases de markdown se houver (```json ... ```)
|
| 104 |
-
try:
|
| 105 |
-
cleaned = re.sub(r'```json|```', '', response_text).strip()
|
| 106 |
-
# Busca novamente na string limpa
|
| 107 |
-
match = re.search(r'\{.*\}', cleaned, re.DOTALL)
|
| 108 |
-
if match:
|
| 109 |
-
data = json.loads(match.group(0))
|
| 110 |
-
if "tool" in data: return data
|
| 111 |
-
except:
|
| 112 |
-
pass
|
| 113 |
-
return None
|
| 114 |
-
except Exception:
|
| 115 |
-
return None
|
| 116 |
-
|
| 117 |
-
def run_tool(self, tool_data):
|
| 118 |
-
name = tool_data["tool"]
|
| 119 |
-
args = tool_data["args"]
|
| 120 |
-
|
| 121 |
-
print(f"⚙️ Executando ferramenta: {name}...")
|
| 122 |
-
|
| 123 |
-
# Intercepta write_file para Review
|
| 124 |
-
if name == "write_file":
|
| 125 |
-
content = args.get("content")
|
| 126 |
-
filepath = args.get("filepath")
|
| 127 |
-
|
| 128 |
-
print(f"🕵️♂️ Solicitando revisão para '{filepath}'...")
|
| 129 |
-
review_result = self.reviewer.review(content, context=f"User asked to create/edit {filepath}")
|
| 130 |
-
|
| 131 |
-
if review_result.get("status") == "REJECTED":
|
| 132 |
-
feedback = review_result.get("feedback", "Sem detalhes.")
|
| 133 |
-
print(f"🛑 REJEITADO pelo Reviewer: {feedback}")
|
| 134 |
-
# Retorna erro simulado para o modelo tentar de novo
|
| 135 |
-
return f"❌ BLOQUEADO PELO REVIEWER. Motivo: {feedback}. Corrija o código e tente salvar novamente."
|
| 136 |
-
else:
|
| 137 |
-
print("✅ APROVADO pelo Reviewer.")
|
| 138 |
-
# Prossegue para salvar
|
| 139 |
-
return self.tools.write_file(filepath, content)
|
| 140 |
-
|
| 141 |
-
if name == "execute_shell":
|
| 142 |
-
return self.tools.execute_shell(args.get("command"))
|
| 143 |
-
elif name == "read_file":
|
| 144 |
-
return self.tools.read_file(args.get("filepath"))
|
| 145 |
-
elif name == "list_files":
|
| 146 |
-
return self.tools.list_files(args.get("path", "."))
|
| 147 |
-
elif name == "run_python":
|
| 148 |
-
return self.tools.run_python(args.get("code"))
|
| 149 |
-
else:
|
| 150 |
-
return f"❌ Ferramenta desconhecida: {name}"
|
| 151 |
-
|
| 152 |
-
def _manage_memory(self):
|
| 153 |
-
"""Mantém o histórico limpo para não estourar tokens."""
|
| 154 |
-
max_ctx = self.cfg.get("max_context", 20)
|
| 155 |
-
if len(self.history) > max_ctx:
|
| 156 |
-
# Mantém sempre o System Prompt (índice 0)
|
| 157 |
-
# E pega as últimas (max_ctx - 1) mensagens
|
| 158 |
-
self.history = [self.history[0]] + self.history[-(max_ctx-1):]
|
| 159 |
-
|
| 160 |
-
def chat_loop(self, user_input):
|
| 161 |
-
"""Ciclo principal de raciocínio (ReAct simplificado)."""
|
| 162 |
-
|
| 163 |
-
# 1. Adiciona input do usuário
|
| 164 |
-
self.history.append({"role": "user", "content": user_input})
|
| 165 |
-
|
| 166 |
-
# Limite de interações no loop para evitar loops infinitos
|
| 167 |
-
max_turns = 5
|
| 168 |
-
turn = 0
|
| 169 |
-
|
| 170 |
-
final_response = ""
|
| 171 |
-
|
| 172 |
-
last_tool_call = None
|
| 173 |
-
consecutive_tool_failures = 0
|
| 174 |
-
|
| 175 |
-
while turn < max_turns:
|
| 176 |
-
# 2. Chama o modelo
|
| 177 |
-
response = self._chat(self.history)
|
| 178 |
-
|
| 179 |
-
# 3. Verifica se é tool call
|
| 180 |
-
tool_data = self.process_tool_call(response)
|
| 181 |
-
|
| 182 |
-
if tool_data:
|
| 183 |
-
# Loop detection
|
| 184 |
-
current_call_signature = f"{tool_data['tool']}:{json.dumps(tool_data['args'], sort_keys=True)}"
|
| 185 |
-
if last_tool_call == current_call_signature:
|
| 186 |
-
consecutive_tool_failures += 1
|
| 187 |
-
if consecutive_tool_failures >= 2:
|
| 188 |
-
error_msg = f"TOOL_ERROR: Detected repetitive loop with same arguments. Stopping tool execution."
|
| 189 |
-
self.history.append({"role": "system", "content": error_msg})
|
| 190 |
-
print(f"🛑 Loop detectado: {error_msg}")
|
| 191 |
-
# Force model to explain
|
| 192 |
-
continue
|
| 193 |
-
else:
|
| 194 |
-
consecutive_tool_failures = 0
|
| 195 |
-
|
| 196 |
-
last_tool_call = current_call_signature
|
| 197 |
-
|
| 198 |
-
# É uma ferramenta -> Executa
|
| 199 |
-
tool_result = self.run_tool(tool_data)
|
| 200 |
-
|
| 201 |
-
# Adiciona a "conversa" da ferramenta no histórico
|
| 202 |
-
self.history.append({"role": "assistant", "content": response})
|
| 203 |
-
self.history.append({"role": "system", "content": f"TOOL_RESULT ({tool_data['tool']}): {tool_result}"})
|
| 204 |
-
|
| 205 |
-
print(f"🔍 Resultado: {str(tool_result)[:200]}...") # Preview
|
| 206 |
-
turn += 1
|
| 207 |
-
else:
|
| 208 |
-
# Resposta final (texto)
|
| 209 |
-
final_response = response
|
| 210 |
-
self.history.append({"role": "assistant", "content": final_response})
|
| 211 |
-
break
|
| 212 |
-
|
| 213 |
-
# Se saiu do loop sem resposta final (max_turns reached)
|
| 214 |
-
if not final_response and turn >= max_turns:
|
| 215 |
-
print("⚠️ Max turns reached without final response. Forcing summary.")
|
| 216 |
-
self.history.append({"role": "system", "content": "SYSTEM: Max tool turns reached. Please summarize what was done and what failed."})
|
| 217 |
-
final_response = self._chat(self.history)
|
| 218 |
-
self.history.append({"role": "assistant", "content": final_response})
|
| 219 |
-
|
| 220 |
-
# Gerencia a memória ao fim do ciclo
|
| 221 |
-
self._manage_memory()
|
| 222 |
-
|
| 223 |
-
return final_response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code_jade/main.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
# Adiciona o diretório raiz ao path para importar módulos corretamente
|
| 5 |
-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 6 |
-
|
| 7 |
-
from code_jade.core import CodeJadeAgent
|
| 8 |
-
|
| 9 |
-
def main():
|
| 10 |
-
print("==========================================")
|
| 11 |
-
print("🚀 CodeJade - AI Developer Agent (v1.0)")
|
| 12 |
-
print("==========================================")
|
| 13 |
-
|
| 14 |
-
try:
|
| 15 |
-
agent = CodeJadeAgent()
|
| 16 |
-
print(f"🔧 Modelo: {agent.cfg.get('groq_model')}")
|
| 17 |
-
print(f"📂 Work Dir: {agent.cfg.get('work_dir')}")
|
| 18 |
-
print("💡 Digite 'sair' para encerrar.\n")
|
| 19 |
-
|
| 20 |
-
while True:
|
| 21 |
-
user_input = input("\n👨💻 Você: ").strip()
|
| 22 |
-
|
| 23 |
-
if not user_input:
|
| 24 |
-
continue
|
| 25 |
-
|
| 26 |
-
if user_input.lower() in ["sair", "exit", "quit"]:
|
| 27 |
-
print("👋 Até logo!")
|
| 28 |
-
break
|
| 29 |
-
|
| 30 |
-
print("🤖 CodeJade pensando...")
|
| 31 |
-
response = agent.chat_loop(user_input)
|
| 32 |
-
print(f"\n🤖 CodeJade: {response}")
|
| 33 |
-
|
| 34 |
-
except KeyboardInterrupt:
|
| 35 |
-
print("\n\n👋 Interrompido pelo usuário.")
|
| 36 |
-
except Exception as e:
|
| 37 |
-
print(f"\n❌ Erro fatal: {e}")
|
| 38 |
-
|
| 39 |
-
if __name__ == "__main__":
|
| 40 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code_jade/requirements.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
groq
|
|
|
|
|
|
code_jade/reviewer.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
import json
|
| 2 |
-
from groq import Groq
|
| 3 |
-
|
| 4 |
-
class CodeReviewer:
|
| 5 |
-
def __init__(self, config):
|
| 6 |
-
self.client = Groq(api_key=self._get_api_key())
|
| 7 |
-
self.model = config.get("groq_model", "llama3-70b-8192")
|
| 8 |
-
|
| 9 |
-
self.system_prompt = """Você é um Code Reviewer Sênior, rigoroso e focado em Segurança e Qualidade.
|
| 10 |
-
Sua função é analisar trechos de código e aprovar ou rejeitar baseando-se em:
|
| 11 |
-
1. Bugs lógicos óbvios.
|
| 12 |
-
2. Riscos de Segurança (ex: SQL Injection, exec sem validação, hardcoded credentials).
|
| 13 |
-
3. Boas práticas (variáveis legíveis, tratamento de erro básico).
|
| 14 |
-
|
| 15 |
-
FORMATO DE RESPOSTA (JSON OBRIGATÓRIO):
|
| 16 |
-
Você deve responder APENAS um JSON no seguinte formato:
|
| 17 |
-
{
|
| 18 |
-
"status": "APPROVED" | "REJECTED",
|
| 19 |
-
"feedback": "Explicação curta do problema (se REJECTED) ou elogio (se APPROVED)."
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
Se o código for seguro e funcional, aprove. Não seja pedante com estilo (PEP8) a menos que torne o código ilegível.
|
| 23 |
-
Se houver perigo real ou erro fatal, rejeite.
|
| 24 |
-
"""
|
| 25 |
-
|
| 26 |
-
def _get_api_key(self):
|
| 27 |
-
import os
|
| 28 |
-
key = os.getenv("GROQ_API_KEY")
|
| 29 |
-
if not key:
|
| 30 |
-
try:
|
| 31 |
-
from google.colab import userdata
|
| 32 |
-
key = userdata.get('GROQ_API_KEY')
|
| 33 |
-
except:
|
| 34 |
-
pass
|
| 35 |
-
return key or "dummy"
|
| 36 |
-
|
| 37 |
-
def review(self, code, context=""):
|
| 38 |
-
"""Analisa o código e retorna status e feedback."""
|
| 39 |
-
messages = [
|
| 40 |
-
{"role": "system", "content": self.system_prompt},
|
| 41 |
-
{"role": "user", "content": f"Contexto: {context}\n\nCódigo para revisão:\n```python\n{code}\n```"}
|
| 42 |
-
]
|
| 43 |
-
|
| 44 |
-
try:
|
| 45 |
-
chat = self.client.chat.completions.create(
|
| 46 |
-
messages=messages,
|
| 47 |
-
model=self.model,
|
| 48 |
-
temperature=0.1, # Baixa temperatura para análise fria
|
| 49 |
-
response_format={"type": "json_object"} # Força JSON mode no Llama 3 se disponível, ou ajuda
|
| 50 |
-
)
|
| 51 |
-
response_text = chat.choices[0].message.content
|
| 52 |
-
|
| 53 |
-
# Tenta parsear
|
| 54 |
-
try:
|
| 55 |
-
return json.loads(response_text)
|
| 56 |
-
except json.JSONDecodeError:
|
| 57 |
-
# Fallback simples se não vier JSON limpo
|
| 58 |
-
if "APPROVED" in response_text.upper():
|
| 59 |
-
return {"status": "APPROVED", "feedback": "Parser failed but looks approved."}
|
| 60 |
-
return {"status": "REJECTED", "feedback": f"Parser failed. Raw response: {response_text}"}
|
| 61 |
-
|
| 62 |
-
except Exception as e:
|
| 63 |
-
return {"status": "ERROR", "feedback": str(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code_jade/tools.py
DELETED
|
@@ -1,127 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import subprocess
|
| 3 |
-
import sys
|
| 4 |
-
import io
|
| 5 |
-
from contextlib import redirect_stdout, redirect_stderr
|
| 6 |
-
|
| 7 |
-
class ToolManager:
|
| 8 |
-
def __init__(self, safe_mode=True, work_dir="."):
|
| 9 |
-
self.safe_mode = safe_mode
|
| 10 |
-
self.work_dir = work_dir
|
| 11 |
-
if not os.path.exists(self.work_dir):
|
| 12 |
-
os.makedirs(self.work_dir, exist_ok=True)
|
| 13 |
-
|
| 14 |
-
def _confirm(self, action, content):
|
| 15 |
-
if not self.safe_mode:
|
| 16 |
-
return True
|
| 17 |
-
|
| 18 |
-
# Check if running in an interactive terminal
|
| 19 |
-
if sys.stdin.isatty():
|
| 20 |
-
print(f"\n⚠️ [SOLICITAÇÃO DE EXECUÇÃO]")
|
| 21 |
-
print(f"Ação: {action}")
|
| 22 |
-
print(f"Detalhes: {content}")
|
| 23 |
-
try:
|
| 24 |
-
resp = input(">> Permitir? (s/n): ").strip().lower()
|
| 25 |
-
return resp == "s"
|
| 26 |
-
except EOFError:
|
| 27 |
-
return False
|
| 28 |
-
else:
|
| 29 |
-
# Non-interactive mode (e.g. server): Auto-deny if safe_mode is True
|
| 30 |
-
# Ideally, this should be configurable, but safe_mode=True implies user MUST confirm.
|
| 31 |
-
# If user can't confirm, we can't proceed safely.
|
| 32 |
-
print(f"⚠️ Blocked action '{action}' in safe_mode (non-interactive environment).")
|
| 33 |
-
return False
|
| 34 |
-
|
| 35 |
-
def execute_shell(self, command):
|
| 36 |
-
"""Executa comandos no terminal."""
|
| 37 |
-
if not self._confirm("SHELL", command):
|
| 38 |
-
return "❌ Ação negada pelo usuário."
|
| 39 |
-
|
| 40 |
-
try:
|
| 41 |
-
# Executa no diretório de trabalho
|
| 42 |
-
result = subprocess.run(
|
| 43 |
-
command,
|
| 44 |
-
shell=True,
|
| 45 |
-
cwd=self.work_dir,
|
| 46 |
-
text=True,
|
| 47 |
-
capture_output=True,
|
| 48 |
-
timeout=30
|
| 49 |
-
)
|
| 50 |
-
output = result.stdout
|
| 51 |
-
if result.stderr:
|
| 52 |
-
output += f"\n[STDERR]\n{result.stderr}"
|
| 53 |
-
return output.strip() or "[Comando executado sem saída]"
|
| 54 |
-
except Exception as e:
|
| 55 |
-
return f"❌ Erro de execução: {str(e)}"
|
| 56 |
-
|
| 57 |
-
def write_file(self, filepath, content):
|
| 58 |
-
"""Cria ou sobrescreve um arquivo."""
|
| 59 |
-
full_path = os.path.join(self.work_dir, filepath)
|
| 60 |
-
|
| 61 |
-
if not self._confirm("WRITE_FILE", f"{filepath} ({len(content)} chars)"):
|
| 62 |
-
return "❌ Ação negada pelo usuário."
|
| 63 |
-
|
| 64 |
-
try:
|
| 65 |
-
# Cria diretórios pai se não existirem
|
| 66 |
-
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
| 67 |
-
with open(full_path, "w", encoding="utf-8") as f:
|
| 68 |
-
f.write(content)
|
| 69 |
-
return f"✅ Arquivo '{filepath}' salvo com sucesso."
|
| 70 |
-
except Exception as e:
|
| 71 |
-
return f"❌ Erro ao salvar arquivo: {str(e)}"
|
| 72 |
-
|
| 73 |
-
def read_file(self, filepath):
|
| 74 |
-
"""Lê o conteúdo de um arquivo."""
|
| 75 |
-
full_path = os.path.join(self.work_dir, filepath)
|
| 76 |
-
try:
|
| 77 |
-
with open(full_path, "r", encoding="utf-8") as f:
|
| 78 |
-
return f.read()
|
| 79 |
-
except FileNotFoundError:
|
| 80 |
-
return f"❌ Arquivo não encontrado: {filepath}"
|
| 81 |
-
except Exception as e:
|
| 82 |
-
return f"❌ Erro ao ler arquivo: {str(e)}"
|
| 83 |
-
|
| 84 |
-
def list_files(self, path="."):
|
| 85 |
-
"""Lista arquivos no diretório."""
|
| 86 |
-
# Se path for '.', usa o diretório atual (que já é o work_dir no execute_shell)
|
| 87 |
-
# Se path for algo mais, concatena
|
| 88 |
-
try:
|
| 89 |
-
cmd = f"ls -R -F {path}"
|
| 90 |
-
return self.execute_shell(cmd)
|
| 91 |
-
except Exception as e:
|
| 92 |
-
return f"❌ Erro ao listar arquivos: {str(e)}"
|
| 93 |
-
|
| 94 |
-
def run_python(self, code):
|
| 95 |
-
"""Executa código Python e captura a saída."""
|
| 96 |
-
if not self._confirm("PYTHON_EXEC", code[:200] + "..."):
|
| 97 |
-
return "❌ Ação negada pelo usuário."
|
| 98 |
-
|
| 99 |
-
# Captura stdout e stderr
|
| 100 |
-
f_out = io.StringIO()
|
| 101 |
-
f_err = io.StringIO()
|
| 102 |
-
|
| 103 |
-
# Sandbox simples: define globais restritos
|
| 104 |
-
# IMPORTANTE: exec() nunca é 100% seguro sem containers reais.
|
| 105 |
-
safe_globals = {
|
| 106 |
-
"__builtins__": __builtins__,
|
| 107 |
-
"__name__": "__main__",
|
| 108 |
-
"math": __import__("math"),
|
| 109 |
-
"json": __import__("json"),
|
| 110 |
-
"os": __import__("os"), # CodeJade precisa de OS muitas vezes, mas é perigoso.
|
| 111 |
-
# Usuário confia no agente, mas evitamos acesso ao `self` do ToolManager
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
try:
|
| 115 |
-
with redirect_stdout(f_out), redirect_stderr(f_err):
|
| 116 |
-
exec(code, safe_globals)
|
| 117 |
-
|
| 118 |
-
output = f_out.getvalue()
|
| 119 |
-
errors = f_err.getvalue()
|
| 120 |
-
|
| 121 |
-
res = ""
|
| 122 |
-
if output: res += f"[STDOUT]\n{output}\n"
|
| 123 |
-
if errors: res += f"[STDERR]\n{errors}\n"
|
| 124 |
-
|
| 125 |
-
return res.strip() or "[Código executado sem saída]"
|
| 126 |
-
except Exception as e:
|
| 127 |
-
return f"❌ Erro de execução Python: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|