Overview
Cloud-based IDEs allow developers to code, run, and debug applications entirely in the browser. The backend manages file systems, provides terminal access, executes code, and handles real-time updates. HopX provides the secure execution environment needed for this use case.Prerequisites
- HopX API key (Get one here)
- Python 3.8+ or Node.js 16+
- Understanding of WebSocket connections
- Basic knowledge of IDE architecture
Architecture
┌──────────────┐
│ Browser │ Editor, Terminal UI
└──────┬───────┘
│ WebSocket/HTTP
▼
┌─────────────────┐
│ IDE Backend │ File ops, Terminal, Execution
└──────┬──────────┘
│
▼
┌─────────────────┐
│ HopX Sandbox │ Secure execution environment
└─────────────────┘
Implementation
Step 1: File System Management
Create a file system manager for the IDE:from hopx_ai import Sandbox
import os
from typing import Dict, List, Any
class IDEFileSystem:
def __init__(self, api_key: str):
self.api_key = api_key
self.sandbox = None
def initialize_project(self, project_id: str) -> Dict[str, Any]:
"""Initialize a new project sandbox"""
try:
self.sandbox = Sandbox.create(
template="code-interpreter",
api_key=self.api_key,
timeout_seconds=3600 # 1 hour for IDE session
)
# Create project structure
self.sandbox.files.mkdir("/workspace/project")
self.sandbox.files.write("/workspace/project/.gitignore", "__pycache__/\n*.pyc\n")
return {
"success": True,
"sandbox_id": self.sandbox.sandbox_id,
"project_path": "/workspace/project"
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def list_files(self, path: str = "/workspace/project") -> List[Dict[str, Any]]:
"""List files and directories"""
try:
files = self.sandbox.files.list(path)
return [
{
"name": f.name,
"path": f.path,
"is_dir": f.is_dir,
"size": f.size,
"modified": f.modified_time.isoformat() if hasattr(f.modified_time, 'isoformat') else str(f.modified_time)
}
for f in files
]
except Exception as e:
return []
def read_file(self, file_path: str) -> Dict[str, Any]:
"""Read file contents"""
try:
content = self.sandbox.files.read(file_path)
return {
"success": True,
"content": content,
"path": file_path
}
except Exception as e:
return {
"success": False,
"error": str(e),
"path": file_path
}
def write_file(self, file_path: str, content: str) -> Dict[str, Any]:
"""Write file contents"""
try:
self.sandbox.files.write(file_path, content)
return {
"success": True,
"path": file_path,
"size": len(content)
}
except Exception as e:
return {
"success": False,
"error": str(e),
"path": file_path
}
def create_directory(self, dir_path: str) -> Dict[str, Any]:
"""Create a directory"""
try:
self.sandbox.files.mkdir(dir_path)
return {
"success": True,
"path": dir_path
}
except Exception as e:
return {
"success": False,
"error": str(e),
"path": dir_path
}
def delete_file(self, file_path: str) -> Dict[str, Any]:
"""Delete a file or directory"""
try:
self.sandbox.files.remove(file_path)
return {
"success": True,
"path": file_path
}
except Exception as e:
return {
"success": False,
"error": str(e),
"path": file_path
}
def cleanup(self):
"""Clean up sandbox"""
if self.sandbox:
self.sandbox.kill()
self.sandbox = None
# Usage
fs = IDEFileSystem(api_key=os.getenv("HOPX_API_KEY"))
result = fs.initialize_project("my-project")
print(result)
# List files
files = fs.list_files()
print(files)
# Create a file
fs.write_file("/workspace/project/main.py", "print('Hello, World!')")
# Read the file
content = fs.read_file("/workspace/project/main.py")
print(content)
fs.cleanup()
import { Sandbox } from '@hopx-ai/sdk';
class IDEFileSystem {
constructor(apiKey) {
this.apiKey = apiKey;
this.sandbox = null;
}
async initializeProject(projectId) {
try {
this.sandbox = await Sandbox.create({
template: 'code-interpreter',
apiKey: this.apiKey,
timeoutSeconds: 3600 // 1 hour for IDE session
});
// Create project structure
await this.sandbox.files.mkdir('/workspace/project');
await this.sandbox.files.write('/workspace/project/.gitignore', '__pycache__/\n*.pyc\n');
return {
success: true,
sandboxId: this.sandbox.sandboxId,
projectPath: '/workspace/project'
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
async listFiles(path = '/workspace/project') {
try {
const files = await this.sandbox.files.list(path);
return files.map(f => ({
name: f.name,
path: f.path,
isDir: f.isDir,
size: f.size,
modified: f.modifiedTime
}));
} catch (error) {
return [];
}
}
async readFile(filePath) {
try {
const content = await this.sandbox.files.read(filePath);
return {
success: true,
content,
path: filePath
};
} catch (error) {
return {
success: false,
error: error.message,
path: filePath
};
}
}
async writeFile(filePath, content) {
try {
await this.sandbox.files.write(filePath, content);
return {
success: true,
path: filePath,
size: content.length
};
} catch (error) {
return {
success: false,
error: error.message,
path: filePath
};
}
}
async createDirectory(dirPath) {
try {
await this.sandbox.files.mkdir(dirPath);
return {
success: true,
path: dirPath
};
} catch (error) {
return {
success: false,
error: error.message,
path: dirPath
};
}
}
async deleteFile(filePath) {
try {
await this.sandbox.files.remove(filePath);
return {
success: true,
path: filePath
};
} catch (error) {
return {
success: false,
error: error.message,
path: filePath
};
}
}
async cleanup() {
if (this.sandbox) {
await this.sandbox.kill();
this.sandbox = null;
}
}
}
// Usage
const fs = new IDEFileSystem(process.env.HOPX_API_KEY);
const result = await fs.initializeProject('my-project');
console.log(result);
// List files
const files = await fs.listFiles();
console.log(files);
// Create a file
await fs.writeFile('/workspace/project/main.py', "print('Hello, World!')");
// Read the file
const content = await fs.readFile('/workspace/project/main.py');
console.log(content);
await fs.cleanup();
Step 2: Terminal Emulation
Implement terminal access using WebSocket:import asyncio
from typing import AsyncIterator, Dict, Any
class IDETerminal:
def __init__(self, sandbox: Sandbox):
self.sandbox = sandbox
self.terminal_session = None
async def connect(self) -> Dict[str, Any]:
"""Connect to terminal"""
try:
self.terminal_session = await self.sandbox.terminal.connect()
return {
"success": True,
"session_id": str(self.terminal_session)
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
async def send_command(self, command: str) -> None:
"""Send command to terminal"""
if self.terminal_session:
await self.sandbox.terminal.send_input(
self.terminal_session,
command + "\n"
)
async def receive_output(self) -> AsyncIterator[Dict[str, Any]]:
"""Receive terminal output"""
if not self.terminal_session:
return
async for message in self.sandbox.terminal.iter_output(self.terminal_session):
yield {
"type": message.get("type", "output"),
"data": message.get("data", ""),
"timestamp": message.get("timestamp")
}
async def resize(self, rows: int, cols: int) -> None:
"""Resize terminal"""
if self.terminal_session:
await self.sandbox.terminal.resize(
self.terminal_session,
rows=rows,
cols=cols
)
async def disconnect(self):
"""Disconnect from terminal"""
self.terminal_session = None
# Usage
sandbox = Sandbox.create(template="code-interpreter", api_key=os.getenv("HOPX_API_KEY"))
terminal = IDETerminal(sandbox)
await terminal.connect()
await terminal.send_command("pwd")
await terminal.send_command("ls -la")
async for output in terminal.receive_output():
print(output)
# Break after some outputs
break
await terminal.disconnect()
sandbox.kill()
class IDETerminal {
constructor(sandbox) {
this.sandbox = sandbox;
this.terminalSession = null;
}
async connect() {
try {
this.terminalSession = await this.sandbox.terminal.connect();
return {
success: true,
sessionId: String(this.terminalSession)
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
async sendCommand(command) {
if (this.terminalSession) {
await this.sandbox.terminal.sendInput(
this.terminalSession,
command + '\n'
);
}
}
async *receiveOutput() {
if (!this.terminalSession) {
return;
}
for await (const message of this.sandbox.terminal.iterOutput(this.terminalSession)) {
yield {
type: message.type || 'output',
data: message.data || '',
timestamp: message.timestamp
};
}
}
async resize(rows, cols) {
if (this.terminalSession) {
await this.sandbox.terminal.resize(
this.terminalSession,
rows,
cols
);
}
}
async disconnect() {
this.terminalSession = null;
}
}
// Usage
const sandbox = await Sandbox.create({
template: 'code-interpreter',
apiKey: process.env.HOPX_API_KEY
});
const terminal = new IDETerminal(sandbox);
await terminal.connect();
await terminal.sendCommand('pwd');
await terminal.sendCommand('ls -la');
for await (const output of terminal.receiveOutput()) {
console.log(output);
// Break after some outputs
break;
}
await terminal.disconnect();
await sandbox.kill();
Step 3: Multi-File Project Execution
Execute projects with multiple files:class IDEProjectExecutor:
def __init__(self, sandbox: Sandbox):
self.sandbox = sandbox
def execute_project(self, entry_point: str, working_dir: str = "/workspace/project") -> Dict[str, Any]:
"""Execute a multi-file project"""
try:
# Determine language from file extension
language = self._detect_language(entry_point)
# Execute in project directory
result = self.sandbox.run_code(
self._get_execution_code(entry_point, language),
language=language,
working_dir=working_dir,
timeout=30
)
return {
"success": result.success,
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.exit_code,
"execution_time": result.execution_time
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _detect_language(self, file_path: str) -> str:
"""Detect language from file extension"""
ext = file_path.split('.')[-1].lower()
language_map = {
'py': 'python',
'js': 'javascript',
'ts': 'typescript',
'java': 'java',
'go': 'go'
}
return language_map.get(ext, 'python')
def _get_execution_code(self, entry_point: str, language: str) -> str:
"""Get code to execute based on language"""
if language == 'python':
return f"exec(open('{entry_point}').read())"
elif language == 'javascript':
return f"require('{entry_point.replace('.js', '')}')"
else:
# For other languages, use appropriate execution method
return f"# Execute {entry_point}"
# Usage
sandbox = Sandbox.create(template="code-interpreter", api_key=os.getenv("HOPX_API_KEY"))
executor = IDEProjectExecutor(sandbox)
# Create a multi-file project
sandbox.files.write("/workspace/project/utils.py", """
def greet(name):
return f"Hello, {name}!"
""")
sandbox.files.write("/workspace/project/main.py", """
from utils import greet
print(greet("World"))
""")
# Execute
result = executor.execute_project("main.py")
print(result)
sandbox.kill()
class IDEProjectExecutor {
constructor(sandbox) {
this.sandbox = sandbox;
}
async executeProject(entryPoint, workingDir = '/workspace/project') {
try {
// Determine language from file extension
const language = this.detectLanguage(entryPoint);
// Execute in project directory
const executionCode = this.getExecutionCode(entryPoint, language);
const result = await this.sandbox.runCode(executionCode, {
language,
workingDir,
timeout: 30
});
return {
success: result.success,
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exit_code,
executionTime: result.execution_time
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
detectLanguage(filePath) {
const ext = filePath.split('.').pop().toLowerCase();
const languageMap = {
'py': 'python',
'js': 'javascript',
'ts': 'typescript',
'java': 'java',
'go': 'go'
};
return languageMap[ext] || 'python';
}
getExecutionCode(entryPoint, language) {
if (language === 'python') {
return `exec(open('${entryPoint}').read())`;
} else if (language === 'javascript') {
return `require('${entryPoint.replace('.js', '')}')`;
} else {
return `# Execute ${entryPoint}`;
}
}
}
// Usage
const sandbox = await Sandbox.create({
template: 'code-interpreter',
apiKey: process.env.HOPX_API_KEY
});
const executor = new IDEProjectExecutor(sandbox);
// Create a multi-file project
await sandbox.files.write('/workspace/project/utils.py', `
def greet(name):
return f"Hello, {name}!"
`);
await sandbox.files.write('/workspace/project/main.py', `
from utils import greet
print(greet("World"))
`);
// Execute
const result = await executor.executeProject('main.py');
console.log(result);
await sandbox.kill();
Step 4: Complete IDE Backend
Combine all components into a complete IDE backend:class CloudIDEBackend:
def __init__(self, api_key: str):
self.api_key = api_key
self.fs = IDEFileSystem(api_key)
self.sandbox = None
self.terminal = None
self.executor = None
def initialize(self, project_id: str) -> Dict[str, Any]:
"""Initialize IDE session"""
result = self.fs.initialize_project(project_id)
if result["success"]:
self.sandbox = self.fs.sandbox
self.terminal = IDETerminal(self.sandbox)
self.executor = IDEProjectExecutor(self.sandbox)
return result
async def handle_file_operation(self, operation: str, **kwargs) -> Dict[str, Any]:
"""Handle file operations"""
if operation == "list":
return {"files": self.fs.list_files(kwargs.get("path", "/workspace/project"))}
elif operation == "read":
return self.fs.read_file(kwargs["path"])
elif operation == "write":
return self.fs.write_file(kwargs["path"], kwargs["content"])
elif operation == "create_dir":
return self.fs.create_directory(kwargs["path"])
elif operation == "delete":
return self.fs.delete_file(kwargs["path"])
else:
return {"success": False, "error": f"Unknown operation: {operation}"}
async def handle_terminal_command(self, command: str) -> Dict[str, Any]:
"""Handle terminal command"""
if not self.terminal.terminal_session:
await self.terminal.connect()
await self.terminal.send_command(command)
# Collect output
outputs = []
async for output in self.terminal.receive_output():
outputs.append(output)
if len(outputs) >= 10: # Limit output
break
return {"outputs": outputs}
def handle_execution(self, entry_point: str) -> Dict[str, Any]:
"""Handle code execution"""
return self.executor.execute_project(entry_point)
def cleanup(self):
"""Clean up all resources"""
if self.terminal:
asyncio.run(self.terminal.disconnect())
self.fs.cleanup()
# Usage
ide = CloudIDEBackend(api_key=os.getenv("HOPX_API_KEY"))
ide.initialize("my-project")
# File operations
result = asyncio.run(ide.handle_file_operation("write", path="/workspace/project/app.py", content="print('Hello')"))
print(result)
# Terminal
result = asyncio.run(ide.handle_terminal_command("ls -la"))
print(result)
# Execution
result = ide.handle_execution("app.py")
print(result)
ide.cleanup()
class CloudIDEBackend {
constructor(apiKey) {
this.apiKey = apiKey;
this.fs = new IDEFileSystem(apiKey);
this.sandbox = null;
this.terminal = null;
this.executor = null;
}
async initialize(projectId) {
const result = await this.fs.initializeProject(projectId);
if (result.success) {
this.sandbox = this.fs.sandbox;
this.terminal = new IDETerminal(this.sandbox);
this.executor = new IDEProjectExecutor(this.sandbox);
}
return result;
}
async handleFileOperation(operation, ...args) {
const kwargs = args[0] || {};
if (operation === 'list') {
return { files: await this.fs.listFiles(kwargs.path || '/workspace/project') };
} else if (operation === 'read') {
return await this.fs.readFile(kwargs.path);
} else if (operation === 'write') {
return await this.fs.writeFile(kwargs.path, kwargs.content);
} else if (operation === 'createDir') {
return await this.fs.createDirectory(kwargs.path);
} else if (operation === 'delete') {
return await this.fs.deleteFile(kwargs.path);
} else {
return { success: false, error: `Unknown operation: ${operation}` };
}
}
async handleTerminalCommand(command) {
if (!this.terminal.terminalSession) {
await this.terminal.connect();
}
await this.terminal.sendCommand(command);
// Collect output
const outputs = [];
for await (const output of this.terminal.receiveOutput()) {
outputs.push(output);
if (outputs.length >= 10) { // Limit output
break;
}
}
return { outputs };
}
async handleExecution(entryPoint) {
return await this.executor.executeProject(entryPoint);
}
async cleanup() {
if (this.terminal) {
await this.terminal.disconnect();
}
await this.fs.cleanup();
}
}
// Usage
const ide = new CloudIDEBackend(process.env.HOPX_API_KEY);
await ide.initialize('my-project');
// File operations
const result1 = await ide.handleFileOperation('write', {
path: '/workspace/project/app.py',
content: "print('Hello')"
});
console.log(result1);
// Terminal
const result2 = await ide.handleTerminalCommand('ls -la');
console.log(result2);
// Execution
const result3 = await ide.handleExecution('app.py');
console.log(result3);
await ide.cleanup();
Best Practices
Security
Always validate file paths to prevent directory traversal attacks. Never allow access to files outside the project directory.
- Path Validation: Sanitize all file paths
- Resource Limits: Set appropriate timeouts and memory limits
- Sandbox Isolation: One sandbox per user/project
- Input Validation: Validate all user inputs
Performance
Use file watching to detect changes and update the UI in real-time, reducing the need for polling.
- Sandbox Reuse: Reuse sandboxes for the duration of an IDE session
- Caching: Cache file listings and metadata
- Async Operations: Use async/await for all I/O operations
- Connection Pooling: Manage WebSocket connections efficiently
User Experience
- Real-Time Updates: Use file watching for live file updates
- Auto-Save: Implement auto-save functionality
- Error Handling: Provide clear error messages
- Progress Indicators: Show execution progress
Real-World Examples
This pattern is used by:- Replit: Online IDE and coding platform
- CodeSandbox: Online code editor and prototyping tool
- Gitpod: Cloud development environments
- GitHub Codespaces: Cloud-based VS Code
Related Cookbooks
- Browser-Based Code Editor - Client-side implementation
- Terminal WebSocket - Terminal integration
- Code Execution - Execution concepts
Next Steps
- Implement WebSocket server for real-time communication
- Add file watching for live updates
- Create a frontend editor interface
- Implement package management (pip, npm, etc.)
- Add debugging support

