| """ |
| DevEnv - Development Environment API for AI Agent |
| Provides: command execution, file operations, web scraping, health checks |
| """ |
| import subprocess |
| import base64 |
| import os |
| import json |
| import tempfile |
| from flask import Flask, request, jsonify, render_template_string |
|
|
| app = Flask(__name__) |
| API_TOKEN = os.environ.get("API_TOKEN", "2004") |
| WORKSPACE = "/home/user/workspace" |
|
|
| def check_auth(): |
| auth = request.headers.get("Authorization", "") |
| if auth == f"Bearer {API_TOKEN}": |
| return True |
| token = request.args.get("token", "") |
| if token == API_TOKEN: |
| return True |
| return False |
|
|
| |
| DASHBOARD_HTML = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>🖥️ DevEnv - Agent Development Environment</title> |
| <meta charset="utf-8"> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { font-family: 'Segoe UI', sans-serif; background: #0d1117; color: #c9d1d9; } |
| .header { background: #161b22; padding: 20px 30px; border-bottom: 1px solid #30363d; } |
| .header h1 { color: #58a6ff; font-size: 24px; } |
| .header p { color: #8b949e; margin-top: 5px; } |
| .container { max-width: 900px; margin: 30px auto; padding: 0 20px; } |
| .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; margin-bottom: 20px; } |
| .card h2 { color: #58a6ff; margin-bottom: 10px; font-size: 18px; } |
| .status { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 600; } |
| .status.ok { background: #238636; color: white; } |
| .terminal { background: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 15px; margin-top: 10px; } |
| .terminal input { width: 80%; background: transparent; border: none; color: #c9d1d9; font-family: monospace; font-size: 14px; outline: none; } |
| .terminal button { background: #238636; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } |
| .terminal pre { margin-top: 10px; color: #8b949e; font-size: 13px; white-space: pre-wrap; max-height: 300px; overflow-y: auto; } |
| .api-list code { background: #0d1117; padding: 3px 8px; border-radius: 4px; font-size: 13px; color: #79c0ff; } |
| .api-list li { margin: 8px 0; line-height: 1.6; } |
| .badge { display: inline-block; background: #1f6feb; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; margin-right: 5px; } |
| </style> |
| </head> |
| <body> |
| <div class="header"> |
| <h1>🖥️ DevEnv — Agent Development Environment</h1> |
| <p>Linux development environment with API control for AI agents</p> |
| </div> |
| <div class="container"> |
| <div class="card"> |
| <h2>📊 Status</h2> |
| <span class="status ok">● Online</span> |
| <p style="margin-top:10px; color:#8b949e">Ubuntu 22.04 • Python 3 • Node.js • Git • Chromium</p> |
| </div> |
| <div class="card"> |
| <h2>💻 Terminal</h2> |
| <div class="terminal"> |
| <span style="color:#238636">$</span> |
| <input type="text" id="cmd" placeholder="Type a command..." onkeypress="if(event.key==='Enter')runCmd()"> |
| <button onclick="runCmd()">Run</button> |
| <pre id="output">Ready.</pre> |
| </div> |
| </div> |
| <div class="card"> |
| <h2>🔌 API Endpoints</h2> |
| <ul class="api-list"> |
| <li><span class="badge">POST</span> <code>/api/exec</code> — Execute shell commands</li> |
| <li><span class="badge">POST</span> <code>/api/read</code> — Read file contents</li> |
| <li><span class="badge">POST</span> <code>/api/write</code> — Write/create files</li> |
| <li><span class="badge">POST</span> <code>/api/browse</code> — Fetch web page content</li> |
| <li><span class="badge">GET</span> <code>/api/health</code> — Health check</li> |
| </ul> |
| </div> |
| </div> |
| <script> |
| async function runCmd() { |
| const cmd = document.getElementById('cmd').value; |
| const out = document.getElementById('output'); |
| out.textContent = 'Running...'; |
| try { |
| const r = await fetch('/api/exec', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer """ + API_TOKEN + """'}, |
| body: JSON.stringify({command: cmd}) |
| }); |
| const d = await r.json(); |
| out.textContent = (d.stdout || '') + (d.stderr ? '\\nSTDERR: ' + d.stderr : ''); |
| if (!out.textContent.trim()) out.textContent = '(no output)'; |
| } catch(e) { out.textContent = 'Error: ' + e; } |
| } |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| @app.route("/") |
| def dashboard(): |
| return render_template_string(DASHBOARD_HTML) |
|
|
| @app.route("/api/health") |
| def health(): |
| return jsonify({"status": "ok", "service": "devenv", "tools": ["bash", "python", "node", "git", "chromium"]}) |
|
|
| @app.route("/api/exec", methods=["POST"]) |
| def exec_cmd(): |
| if not check_auth(): |
| return jsonify({"error": "unauthorized"}), 401 |
| data = request.get_json() or {} |
| cmd = data.get("command", "echo hello") |
| timeout = min(data.get("timeout", 30), 120) |
| cwd = data.get("cwd", WORKSPACE) |
| try: |
| result = subprocess.run( |
| cmd, shell=True, capture_output=True, text=True, |
| timeout=timeout, cwd=cwd, |
| env={**os.environ, "HOME": "/home/user"} |
| ) |
| return jsonify({ |
| "stdout": result.stdout[-10000:], |
| "stderr": result.stderr[-3000:], |
| "returncode": result.returncode |
| }) |
| except subprocess.TimeoutExpired: |
| return jsonify({"error": "timeout", "stdout": "", "stderr": f"Command timed out after {timeout}s"}) |
| except Exception as e: |
| return jsonify({"error": str(e)}) |
|
|
| @app.route("/api/read", methods=["POST"]) |
| def read_file(): |
| if not check_auth(): |
| return jsonify({"error": "unauthorized"}), 401 |
| data = request.get_json() or {} |
| path = data.get("path", "") |
| if not path: |
| return jsonify({"error": "path required"}) |
| try: |
| with open(path, "r") as f: |
| content = f.read(100000) |
| return jsonify({"content": content, "path": path}) |
| except Exception as e: |
| return jsonify({"error": str(e)}) |
|
|
| @app.route("/api/write", methods=["POST"]) |
| def write_file(): |
| if not check_auth(): |
| return jsonify({"error": "unauthorized"}), 401 |
| data = request.get_json() or {} |
| path = data.get("path", "") |
| content = data.get("content", "") |
| if not path: |
| return jsonify({"error": "path required"}) |
| try: |
| os.makedirs(os.path.dirname(path) or ".", exist_ok=True) |
| with open(path, "w") as f: |
| f.write(content) |
| return jsonify({"status": "ok", "path": path, "bytes": len(content)}) |
| except Exception as e: |
| return jsonify({"error": str(e)}) |
|
|
| @app.route("/api/browse", methods=["POST"]) |
| def browse(): |
| if not check_auth(): |
| return jsonify({"error": "unauthorized"}), 401 |
| data = request.get_json() or {} |
| url = data.get("url", "") |
| if not url: |
| return jsonify({"error": "url required"}) |
| try: |
| import requests as req |
| r = req.get(url, timeout=15, headers={"User-Agent": "Mozilla/5.0"}) |
| from bs4 import BeautifulSoup |
| soup = BeautifulSoup(r.text, "html.parser") |
| |
| for s in soup(["script", "style"]): |
| s.decompose() |
| text = soup.get_text(separator="\n", strip=True) |
| return jsonify({ |
| "url": url, |
| "status_code": r.status_code, |
| "title": soup.title.string if soup.title else "", |
| "text": text[:15000], |
| "html_length": len(r.text) |
| }) |
| except Exception as e: |
| return jsonify({"error": str(e)}) |
|
|
| if __name__ == "__main__": |
| os.makedirs(WORKSPACE, exist_ok=True) |
| print("🖥️ DevEnv API starting on port 7860...") |
| app.run(host="0.0.0.0", port=7860) |
|
|