| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Manus AI Terminal</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
| background: #0d1117; |
| color: #c9d1d9; |
| height: 100vh; |
| overflow: hidden; |
| } |
| |
| .terminal-container { |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| background: linear-gradient(135deg, #0d1117 0%, #161b22 100%); |
| border: 1px solid #30363d; |
| } |
| |
| .terminal-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 12px 16px; |
| background: #161b22; |
| border-bottom: 1px solid #30363d; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); |
| } |
| |
| .terminal-title { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 14px; |
| font-weight: 600; |
| color: #f0f6fc; |
| } |
| |
| .terminal-icon { |
| width: 16px; |
| height: 16px; |
| background: #238636; |
| border-radius: 50%; |
| position: relative; |
| } |
| |
| .terminal-icon::after { |
| content: '>'; |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| font-size: 10px; |
| color: white; |
| font-weight: bold; |
| } |
| |
| .terminal-controls { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .control-btn { |
| width: 12px; |
| height: 12px; |
| border-radius: 50%; |
| border: none; |
| cursor: pointer; |
| transition: opacity 0.2s; |
| } |
| |
| .control-btn:hover { |
| opacity: 0.8; |
| } |
| |
| .close { background: #ff5f56; } |
| .minimize { background: #ffbd2e; } |
| .maximize { background: #27ca3f; } |
| |
| .terminal-body { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| .terminal-output { |
| flex: 1; |
| padding: 16px; |
| overflow-y: auto; |
| font-size: 13px; |
| line-height: 1.4; |
| background: #0d1117; |
| scrollbar-width: thin; |
| scrollbar-color: #30363d #0d1117; |
| } |
| |
| .terminal-output::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .terminal-output::-webkit-scrollbar-track { |
| background: #0d1117; |
| } |
| |
| .terminal-output::-webkit-scrollbar-thumb { |
| background: #30363d; |
| border-radius: 4px; |
| } |
| |
| .terminal-output::-webkit-scrollbar-thumb:hover { |
| background: #484f58; |
| } |
| |
| .terminal-line { |
| margin-bottom: 2px; |
| white-space: pre-wrap; |
| word-wrap: break-word; |
| } |
| |
| .command-line { |
| color: #58a6ff; |
| font-weight: 600; |
| } |
| |
| .output-line { |
| color: #c9d1d9; |
| } |
| |
| .error-line { |
| color: #f85149; |
| } |
| |
| .success-line { |
| color: #56d364; |
| } |
| |
| .system-line { |
| color: #ffa657; |
| font-style: italic; |
| } |
| |
| .timestamp { |
| color: #7d8590; |
| font-size: 11px; |
| margin-right: 8px; |
| } |
| |
| .terminal-input { |
| display: flex; |
| align-items: center; |
| padding: 12px 16px; |
| background: #161b22; |
| border-top: 1px solid #30363d; |
| } |
| |
| .prompt { |
| color: #58a6ff; |
| margin-right: 8px; |
| font-weight: 600; |
| } |
| |
| .input-field { |
| flex: 1; |
| background: transparent; |
| border: none; |
| color: #c9d1d9; |
| font-family: inherit; |
| font-size: 13px; |
| outline: none; |
| } |
| |
| .input-field::placeholder { |
| color: #7d8590; |
| } |
| |
| .status-indicator { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-left: 12px; |
| } |
| |
| .status-dot { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: #7d8590; |
| transition: background-color 0.3s; |
| } |
| |
| .status-dot.connected { |
| background: #56d364; |
| box-shadow: 0 0 8px rgba(86, 211, 100, 0.5); |
| } |
| |
| .status-dot.running { |
| background: #ffa657; |
| animation: pulse 1.5s infinite; |
| } |
| |
| .status-dot.error { |
| background: #f85149; |
| } |
| |
| @keyframes pulse { |
| 0%, 100% { opacity: 1; } |
| 50% { opacity: 0.5; } |
| } |
| |
| .typing-indicator { |
| display: none; |
| color: #7d8590; |
| font-style: italic; |
| animation: blink 1s infinite; |
| } |
| |
| @keyframes blink { |
| 0%, 50% { opacity: 1; } |
| 51%, 100% { opacity: 0; } |
| } |
| |
| .command-history { |
| position: absolute; |
| bottom: 60px; |
| left: 16px; |
| right: 16px; |
| background: #21262d; |
| border: 1px solid #30363d; |
| border-radius: 6px; |
| max-height: 200px; |
| overflow-y: auto; |
| display: none; |
| z-index: 1000; |
| } |
| |
| .history-item { |
| padding: 8px 12px; |
| cursor: pointer; |
| border-bottom: 1px solid #30363d; |
| transition: background-color 0.2s; |
| } |
| |
| .history-item:hover { |
| background: #30363d; |
| } |
| |
| .history-item:last-child { |
| border-bottom: none; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .terminal-header { |
| padding: 8px 12px; |
| } |
| |
| .terminal-output { |
| padding: 12px; |
| font-size: 12px; |
| } |
| |
| .terminal-input { |
| padding: 8px 12px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="terminal-container"> |
| <div class="terminal-header"> |
| <div class="terminal-title"> |
| <div class="terminal-icon"></div> |
| <span>Manus AI Terminal</span> |
| </div> |
| <div class="terminal-controls"> |
| <button class="control-btn close" onclick="closeTerminal()"></button> |
| <button class="control-btn minimize" onclick="minimizeTerminal()"></button> |
| <button class="control-btn maximize" onclick="maximizeTerminal()"></button> |
| </div> |
| </div> |
| |
| <div class="terminal-body"> |
| <div class="terminal-output" id="output"></div> |
| <div class="command-history" id="history"></div> |
| |
| <div class="terminal-input"> |
| <span class="prompt">$</span> |
| <input type="text" class="input-field" id="commandInput" |
| placeholder="Type a command and press Enter..." |
| autocomplete="off" spellcheck="false"> |
| <div class="status-indicator"> |
| <div class="status-dot" id="statusDot"></div> |
| <span id="statusText">Disconnected</span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| class ManusTerminal { |
| constructor() { |
| this.ws = null; |
| this.output = document.getElementById('output'); |
| this.input = document.getElementById('commandInput'); |
| this.statusDot = document.getElementById('statusDot'); |
| this.statusText = document.getElementById('statusText'); |
| this.history = document.getElementById('history'); |
| |
| this.commandHistory = []; |
| this.historyIndex = -1; |
| this.isConnected = false; |
| this.isRunning = false; |
| |
| this.init(); |
| } |
| |
| init() { |
| this.setupEventListeners(); |
| this.connect(); |
| this.addWelcomeMessage(); |
| } |
| |
| setupEventListeners() { |
| this.input.addEventListener('keydown', (e) => this.handleKeyDown(e)); |
| this.input.addEventListener('keyup', (e) => this.handleKeyUp(e)); |
| |
| |
| window.addEventListener('focus', () => { |
| if (!this.isConnected) { |
| this.connect(); |
| } |
| }); |
| } |
| |
| connect() { |
| try { |
| this.ws = new WebSocket('ws://localhost:8765'); |
| |
| this.ws.onopen = () => { |
| this.isConnected = true; |
| this.updateStatus('connected', 'Connected'); |
| this.addSystemMessage('π Connected to terminal server'); |
| }; |
| |
| this.ws.onmessage = (event) => { |
| const data = JSON.parse(event.data); |
| this.handleMessage(data); |
| }; |
| |
| this.ws.onclose = () => { |
| this.isConnected = false; |
| this.isRunning = false; |
| this.updateStatus('error', 'Disconnected'); |
| this.addSystemMessage('β Connection lost. Attempting to reconnect...'); |
| |
| |
| setTimeout(() => this.connect(), 3000); |
| }; |
| |
| this.ws.onerror = (error) => { |
| this.addSystemMessage('β οΈ Connection error. Check if the server is running.'); |
| }; |
| |
| } catch (error) { |
| this.addSystemMessage('β Failed to connect to terminal server'); |
| } |
| } |
| |
| handleMessage(data) { |
| const timestamp = new Date(data.timestamp).toLocaleTimeString(); |
| |
| switch (data.type) { |
| case 'connected': |
| this.addSystemMessage(data.message); |
| break; |
| |
| case 'command_start': |
| this.isRunning = true; |
| this.updateStatus('running', 'Running'); |
| this.addCommandLine(data.message); |
| break; |
| |
| case 'output': |
| this.addOutputLine(data.data, data.stream); |
| break; |
| |
| case 'command_complete': |
| this.isRunning = false; |
| this.updateStatus('connected', 'Connected'); |
| this.addSystemMessage(`Process completed with exit code ${data.exit_code}`); |
| break; |
| |
| case 'error': |
| this.addErrorLine(data.data); |
| break; |
| |
| case 'interrupted': |
| this.isRunning = false; |
| this.updateStatus('connected', 'Connected'); |
| this.addSystemMessage(data.message); |
| break; |
| } |
| } |
| |
| handleKeyDown(e) { |
| switch (e.key) { |
| case 'Enter': |
| e.preventDefault(); |
| this.executeCommand(); |
| break; |
| |
| case 'ArrowUp': |
| e.preventDefault(); |
| this.navigateHistory(-1); |
| break; |
| |
| case 'ArrowDown': |
| e.preventDefault(); |
| this.navigateHistory(1); |
| break; |
| |
| case 'Tab': |
| e.preventDefault(); |
| |
| break; |
| |
| case 'c': |
| if (e.ctrlKey) { |
| e.preventDefault(); |
| this.interruptCommand(); |
| } |
| break; |
| } |
| } |
| |
| handleKeyUp(e) { |
| |
| if (e.target.value.length > 0) { |
| |
| } |
| } |
| |
| executeCommand() { |
| const command = this.input.value.trim(); |
| if (!command || !this.isConnected) return; |
| |
| |
| if (this.commandHistory[this.commandHistory.length - 1] !== command) { |
| this.commandHistory.push(command); |
| } |
| this.historyIndex = this.commandHistory.length; |
| |
| |
| this.ws.send(JSON.stringify({ |
| type: 'command', |
| command: command |
| })); |
| |
| |
| this.input.value = ''; |
| } |
| |
| interruptCommand() { |
| if (this.isRunning && this.isConnected) { |
| this.ws.send(JSON.stringify({ |
| type: 'interrupt' |
| })); |
| } |
| } |
| |
| navigateHistory(direction) { |
| if (this.commandHistory.length === 0) return; |
| |
| this.historyIndex += direction; |
| |
| if (this.historyIndex < 0) { |
| this.historyIndex = 0; |
| } else if (this.historyIndex >= this.commandHistory.length) { |
| this.historyIndex = this.commandHistory.length; |
| this.input.value = ''; |
| return; |
| } |
| |
| this.input.value = this.commandHistory[this.historyIndex] || ''; |
| } |
| |
| updateStatus(status, text) { |
| this.statusDot.className = `status-dot ${status}`; |
| this.statusText.textContent = text; |
| } |
| |
| addWelcomeMessage() { |
| this.addSystemMessage('π― Manus AI Terminal - Ready for commands'); |
| this.addSystemMessage('π‘ Use Ctrl+C to interrupt running commands'); |
| this.addSystemMessage('π Use β/β arrows to navigate command history'); |
| } |
| |
| addCommandLine(text) { |
| this.addLine(text, 'command-line'); |
| } |
| |
| addOutputLine(text, stream = 'stdout') { |
| const className = stream === 'stderr' ? 'error-line' : 'output-line'; |
| this.addLine(text, className); |
| } |
| |
| addErrorLine(text) { |
| this.addLine(text, 'error-line'); |
| } |
| |
| addSystemMessage(text) { |
| this.addLine(text, 'system-line'); |
| } |
| |
| addLine(text, className = 'output-line') { |
| const line = document.createElement('div'); |
| line.className = `terminal-line ${className}`; |
| |
| const timestamp = document.createElement('span'); |
| timestamp.className = 'timestamp'; |
| timestamp.textContent = new Date().toLocaleTimeString(); |
| |
| const content = document.createElement('span'); |
| content.textContent = text; |
| |
| line.appendChild(timestamp); |
| line.appendChild(content); |
| |
| this.output.appendChild(line); |
| this.scrollToBottom(); |
| } |
| |
| scrollToBottom() { |
| this.output.scrollTop = this.output.scrollHeight; |
| } |
| |
| clear() { |
| this.output.innerHTML = ''; |
| this.addWelcomeMessage(); |
| } |
| } |
| |
| |
| function closeTerminal() { |
| if (confirm('Are you sure you want to close the terminal?')) { |
| window.close(); |
| } |
| } |
| |
| function minimizeTerminal() { |
| |
| console.log('Minimize terminal'); |
| } |
| |
| function maximizeTerminal() { |
| if (document.fullscreenElement) { |
| document.exitFullscreen(); |
| } else { |
| document.documentElement.requestFullscreen(); |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| window.terminal = new ManusTerminal(); |
| }); |
| |
| |
| window.addEventListener('keydown', (e) => { |
| if (e.ctrlKey && e.key === 'l') { |
| e.preventDefault(); |
| window.terminal.clear(); |
| } |
| }); |
| </script> |
| </body> |
| </html> |