import "@xterm/xterm/css/xterm.css"; const THEME = { background: "#10141c", foreground: "#d6deeb", cursor: "#f7c948", cursorAccent: "#10141c", selectionBackground: "#334155", black: "#111827", red: "#f87171", green: "#34d399", yellow: "#facc15", blue: "#60a5fa", magenta: "#c084fc", cyan: "#22d3ee", white: "#f8fafc", brightBlack: "#475569", brightRed: "#fca5a5", brightGreen: "#86efac", brightYellow: "#fde68a", brightBlue: "#93c5fd", brightMagenta: "#d8b4fe", brightCyan: "#67e8f9", brightWhite: "#ffffff", }; function normalizeNewlines(text) { return String(text).replace(/\r?\n/g, "\r\n"); } function withTimeout(promise, ms, label) { return Promise.race([ promise, new Promise((_, reject) => { setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms); }), ]); } export async function createWebTerminal(container) { let terminal; let fitAddon; let engine = "ghostty-web"; try { const ghostty = await withTimeout(import("ghostty-web"), 3000, "ghostty-web import"); await withTimeout(ghostty.init(), 3000, "ghostty-web init"); terminal = new ghostty.Terminal({ cursorBlink: true, fontFamily: "JetBrains Mono, SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace", fontSize: 14, rows: 30, cols: 100, scrollback: 5000, theme: THEME, }); fitAddon = new ghostty.FitAddon(); terminal.loadAddon(fitAddon); } catch (error) { const [{ Terminal }, { FitAddon }] = await Promise.all([import("@xterm/xterm"), import("@xterm/addon-fit")]); engine = "xterm"; terminal = new Terminal({ cursorBlink: true, fontFamily: "JetBrains Mono, SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace", fontSize: 14, rows: 30, cols: 100, scrollback: 5000, theme: THEME, }); fitAddon = new FitAddon(); terminal.loadAddon(fitAddon); console.warn("ghostty-web failed, using xterm fallback", error); } terminal.open(container); fitAddon.fit(); terminal.focus(); const resizeObserver = new ResizeObserver(() => fitAddon.fit()); resizeObserver.observe(container); return { engine, raw: terminal, onData: (handler) => terminal.onData(handler), write: (text) => terminal.write(normalizeNewlines(text)), writeln: (text = "") => terminal.write(`${normalizeNewlines(text)}\r\n`), clear: () => terminal.clear(), focus: () => terminal.focus(), fit: () => fitAddon.fit(), dispose: () => { resizeObserver.disconnect(); terminal.dispose(); }, }; }