Skip to main content
Glama
masx200

Persistent Terminal MCP Server

by masx200
terminal.js6.56 kB
// Terminal Detail Page Logic let term = null; let ws = null; let terminalId = null; let currentCursor = 0; // Initialize document.addEventListener("DOMContentLoaded", () => { // Get terminal ID from URL const pathParts = window.location.pathname.split("/"); terminalId = pathParts[pathParts.length - 1]; if (!terminalId) { alert("Invalid terminal ID"); window.location.href = "/"; return; } setupTerminal(); setupEventListeners(); connectWebSocket(); loadTerminalInfo(); loadTerminalOutput(); }); // Setup xterm.js function setupTerminal() { try { term = new Terminal({ cursorBlink: true, fontSize: 14, fontFamily: 'Menlo, Monaco, "Courier New", monospace', theme: { background: "#000000", foreground: "#ffffff", cursor: "#ffffff", selection: "#ffffff40", }, convertEol: true, rows: 24, cols: 80, }); const container = document.getElementById("terminal-container"); term.open(container); // Fit terminal to container (optional, fallback if FitAddon not available) if (typeof FitAddon !== "undefined") { try { const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); fitAddon.fit(); // Resize on window resize window.addEventListener("resize", () => { fitAddon.fit(); }); } catch (e) { console.warn("FitAddon not available:", e); } } console.log("Terminal initialized successfully"); } catch (error) { console.error("Failed to setup terminal:", error); alert("Failed to initialize terminal: " + error.message); } } // Setup event listeners function setupEventListeners() { document.getElementById("send-btn").addEventListener("click", sendCommand); document.getElementById("command-input").addEventListener("keypress", (e) => { if (e.key === "Enter") { sendCommand(); } }); document.getElementById("clear-btn").addEventListener("click", () => { term.clear(); }); document.getElementById("kill-btn").addEventListener("click", killTerminal); } // WebSocket connection function connectWebSocket() { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${protocol}//${window.location.host}`; ws = new WebSocket(wsUrl); ws.onopen = () => { console.log("WebSocket connected"); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); handleWebSocketMessage(message); }; ws.onerror = (error) => { console.error("WebSocket error:", error); }; ws.onclose = () => { console.log("WebSocket disconnected, reconnecting..."); setTimeout(connectWebSocket, 2000); }; } // Handle WebSocket messages function handleWebSocketMessage(message) { if (message.terminalId !== terminalId) return; switch (message.type) { case "output": term.write(message.data); break; case "exit": updateStatus("terminated"); term.write("\r\n\x1b[31m[Terminal Exited]\x1b[0m\r\n"); break; } } // Load terminal info async function loadTerminalInfo() { try { console.log("Loading terminal info for:", terminalId); const response = await fetch(`/api/terminals/${terminalId}`); console.log("Response status:", response.status); if (!response.ok) { const errorText = await response.text(); console.error("Error response:", errorText); throw new Error(`Terminal not found (${response.status})`); } const data = await response.json(); console.log("Terminal data:", data); document.getElementById("terminal-title").textContent = `Terminal ${data.id.substring(0, 8)}`; document.getElementById("detail-pid").textContent = data.pid; document.getElementById("detail-shell").textContent = data.shell; document.getElementById("detail-cwd").textContent = data.cwd; document.getElementById("detail-created").textContent = new Date( data.created, ).toLocaleString(); updateStatus(data.status); console.log("Terminal info loaded successfully"); } catch (error) { console.error("Failed to load terminal info:", error); alert("Failed to load terminal: " + error.message); // Don't redirect immediately, let user see the error // window.location.href = '/'; } } // Load terminal output async function loadTerminalOutput() { try { console.log("Loading terminal output for:", terminalId); const response = await fetch( `/api/terminals/${terminalId}/output?since=${currentCursor}`, ); if (!response.ok) { const errorText = await response.text(); console.error("Failed to load output:", errorText); throw new Error("Failed to load output"); } const data = await response.json(); console.log("Output data:", data); if (data.output) { term.write(data.output); console.log("Wrote output to terminal"); } else { console.log("No output to display"); } currentCursor = data.cursor || data.since || 0; console.log("Current cursor:", currentCursor); } catch (error) { console.error("Failed to load terminal output:", error); } } // Send command async function sendCommand() { const input = document.getElementById("command-input"); const command = input.value; if (!command.trim()) return; try { const response = await fetch(`/api/terminals/${terminalId}/input`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ input: command }), }); if (!response.ok) { throw new Error("Failed to send command"); } input.value = ""; } catch (error) { console.error("Failed to send command:", error); alert("Failed to send command: " + error.message); } } // Kill terminal async function killTerminal() { if (!confirm("Are you sure you want to kill this terminal?")) { return; } try { const response = await fetch(`/api/terminals/${terminalId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Failed to kill terminal"); } alert("Terminal killed"); window.location.href = "/"; } catch (error) { console.error("Failed to kill terminal:", error); alert("Failed to kill terminal: " + error.message); } } // Update status badge function updateStatus(status) { const badge = document.getElementById("terminal-status"); badge.textContent = status; badge.className = "status-badge status-" + status; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/masx200/persistent-terminal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server