### Install Dependencies and Run Dev Server Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Install project dependencies using Bun and start the Vite development server for local development. ```bash bun install bun run dev ``` -------------------------------- ### Start Interactive Terminal Demo Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Set up and run the interactive terminal demo by starting the PTY server and the Vite web server. ```bash cd demo/server && bun install && bun run start bun run dev ``` -------------------------------- ### Start Vite Dev Server for Demos Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Start the Vite development server, which is required for running demos due to its support for TypeScript imports. Access the demo at http://localhost:8000/demo/. ```bash bun run dev ``` -------------------------------- ### Start Demo Server on Custom Port Source: https://github.com/coder/ghostty-web/blob/main/demo/README.md Start the demo server on a custom port by setting the PORT environment variable. The default port is 8080. ```bash PORT=3000 npx @ghostty-web/demo@next ``` -------------------------------- ### Install Ghostty-Web Source: https://github.com/coder/ghostty-web/blob/main/README.md Install the ghostty-web package using npm. This command adds the library to your project dependencies. ```bash npm install ghostty-web ``` -------------------------------- ### Start PTY Server for Interactive Demos Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Run the PTY server to enable interactive terminal demos. Navigate to the server directory and start the server using Bun. The WebSocket will connect to the specified port. ```bash # Terminal needs PTY server running cd demo/server bun run start ``` -------------------------------- ### Run Ghostty-Web Demo Source: https://github.com/coder/ghostty-web/blob/main/README.md Execute this command to start a local HTTP server with a real shell for testing ghostty-web. It works best on Linux and macOS. ```bash npx @ghostty-web/demo@next ``` -------------------------------- ### Expose Demo Server with ngrok Source: https://github.com/coder/ghostty-web/blob/main/demo/README.md This example shows how to expose the locally running demo server to the internet using ngrok. Ensure the demo server is running in one terminal before executing the ngrok command in another. ```bash # Start the demo server npx @ghostty-web/demo@next # In another terminal, expose it via ngrok ngrok http 8080 ``` -------------------------------- ### Build Ghostty-Web Source: https://github.com/coder/ghostty-web/blob/main/README.md Build the ghostty-web project using Bun. This command is part of the development process and requires Zig and Bun to be installed. ```bash bun run build ``` -------------------------------- ### Start Vite Development Server Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Use the Vite development server to transpile TypeScript files during development. This is essential for demos that import TypeScript modules directly. ```bash # ✅ Works - Vite transpiles TypeScript bun run dev ``` -------------------------------- ### Display RGB True Color (24-bit) Source: https://github.com/coder/ghostty-web/blob/main/demo/colors-demo.html Demonstrates RGB true color (24-bit) support in the terminal, showing a rainbow gradient, custom color examples, and gradient bars for red, green, and blue. Also includes examples of RGB backgrounds. ```javascript window.showRGBColors = function () { term.clear(); writeln('\x1b[1;36m═══ RGB True Color (24-bit) ═══\x1b[0m'); writeln(''); writeln('\x1b[1mRGB Color Examples:\x1b[0m'); writeln(''); // Rainbow gradient writeln('Rainbow Gradient:'); const rainbowColors = [ [255, 0, 0], // Red [255, 127, 0], // Orange [255, 255, 0], // Yellow [0, 255, 0], // Green [0, 0, 255], // Blue [75, 0, 130], // Indigo [148, 0, 211], // Violet ]; let line = ''; for (const [r, g, b] of rainbowColors) { line += `\x1b[38;2;${r};${g};${b}m████\x1b[0m `; } writeln(line); writeln(''); // Specific RGB examples writeln('\x1b[1mCustom RGB Colors:\x1b[0m'); writeln('\x1b[38;2;255;105;180mHot Pink (255, 105, 180)\x1b[0m'); writeln('\x1b[38;2;64;224;208mTurquoise (64, 224, 208)\x1b[0m'); writeln('\x1b[38;2;255;215;0mGold (255, 215, 0)\x1b[0m'); writeln('\x1b[38;2;138;43;226mBlue Violet (138, 43, 226)\x1b[0m'); writeln('\x1b[38;2;0;128;0mForest Green (0, 128, 0)\x1b[0m'); writeln(''); // Gradient bars writeln('\x1b[1mRed Gradient:\x1b[0m'); line = ''; for (let i = 0; i <= 255; i += 8) { line += `\x1b[38;2;${i};0;0m█\x1b[0m`; } writeln(line); writeln(''); writeln('\x1b[1mGreen Gradient:\x1b[0m'); line = ''; for (let i = 0; i <= 255; i += 8) { line += `\x1b[38;2;0;${i};0m█\x1b[0m`; } writeln(line); writeln(''); writeln('\x1b[1mBlue Gradient:\x1b[0m'); line = ''; for (let i = 0; i <= 255; i += 8) { line += `\x1b[38;2;0;0;${i}m█\x1b[0m`; } writeln(line); writeln(''); // Background examples writeln('\x1b[1mRGB Backgrounds:\x1b[0m'); writeln('\x1b[48;2;220;20;60m\x1b[37m Crimson background \x1b[0m'); writeln('\x1b[48;2;46;139;87m\x1b[37m Sea green background \x1b[0m'); writeln('\x1b[48;2;70;130;180m\x1b[37m Steel blue background \x1b[0m'); writeln(''); }; ``` -------------------------------- ### Start Python HTTP Server (Incorrect for TS Imports) Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Avoid using Python's built-in HTTP server for demos that import TypeScript modules directly, as it cannot transpile them. This method is suitable for serving static assets only. ```bash # ❌ Fails - Browser can't load .ts files directly python3 -m http.server ``` -------------------------------- ### Open Terminal in DOM Source: https://context7.com/coder/ghostty-web/llms.txt The `open()` method attaches the terminal to a DOM element, initializes the canvas renderer, and starts the render loop. Ensure `init()` is called first and the container element exists. ```javascript import { init, Terminal } from 'ghostty-web'; await init(); const term = new Terminal({ fontSize: 14, theme: { background: '#1e1e1e', foreground: '#d4d4d4', }, }); // Open terminal in a container element const container = document.getElementById('terminal-container'); term.open(container); // Terminal is now ready to use term.write('Hello, World!\r\n'); term.focus(); ``` -------------------------------- ### Write Unit Tests for Terminal Features Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Structure tests using describe, test, and expect from 'bun:test'. Ensure proper setup and teardown by opening and disposing of the terminal instance. Access the WASM API directly for state verification. ```typescript import { describe, test, expect } from 'bun:test'; describe('MyFeature', () => { test('should do something', async () => { const term = new Terminal({ cols: 80, rows: 24 }); const container = document.createElement('div'); await term.open(container); term.write('test\r\n'); // Check WASM state const cursor = term.wasmTerm!.getCursor(); expect(cursor.y).toBe(1); term.dispose(); }); }); ``` -------------------------------- ### Get Terminal Viewport and Scrollback Info Source: https://context7.com/coder/ghostty-web/llms.txt Retrieves the current viewport position and calculates the total scrollback lines available in the terminal buffer. ```javascript // Get current viewport position const viewportY = term.getViewportY(); console.log('Lines scrolled back:', viewportY); // Get scrollback info from buffer const scrollbackLength = term.buffer.active.length - term.rows; console.log('Total scrollback lines:', scrollbackLength); ``` -------------------------------- ### Initialize and Use Ghostty-Web Terminal Source: https://github.com/coder/ghostty-web/blob/main/README.md This JavaScript snippet demonstrates how to initialize ghostty-web, create a new terminal instance, open it in a DOM element, and handle data input/output via a WebSocket. Ensure the 'init()' function is awaited before creating a Terminal instance. ```javascript import { init, Terminal } from 'ghostty-web'; await init(); const term = new Terminal({ fontSize: 14, theme: { background: '#1a1b26', foreground: '#a9b1d6', }, }); term.open(document.getElementById('terminal')); term.onData((data) => websocket.send(data)); websocket.onmessage = (e) => term.write(e.data); ``` -------------------------------- ### Initialize Ghostty Terminal and FitAddon Source: https://github.com/coder/ghostty-web/blob/main/demo/index.html Initializes the WASM module, creates a new Terminal instance with custom theme and settings, loads the FitAddon for automatic resizing, and opens the terminal in the specified container. It also sets up event listeners for terminal resizing and user input. ```typescript import { init, Terminal } from '../lib/index.ts'; import { FitAddon } from '../lib/addons/fit.ts'; let term; let ws; let fitAddon; async function initTerminal() { // Initialize WASM await init(); term = new Terminal({ cursorBlink: true, fontSize: 14, fontFamily: 'Monaco, Menlo, "Courier New", monospace', theme: { background: '#1e1e1e', foreground: '#d4d4d4', }, scrollback: 10000, }); fitAddon = new FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById('terminal-container')); fitAddon.fit(); fitAddon.observeResize(); // Auto-fit when container resizes // Handle window resize (for browsers that don't trigger ResizeObserver on window resize) window.addEventListener('resize', () => { fitAddon.fit(); }); // Handle terminal resize term.onResize((size) => { if (ws && ws.readyState === WebSocket.OPEN) { // Send resize as control sequence (server expects this format) ws.send(JSON.stringify({ type: 'resize', cols: size.cols, rows: size.rows })); } }); // Handle user input term.onData((data) => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(data); } }); // Debug scrollback console.log('Terminal scrollback:', term.buffer?.scrollback?.length || 'N/A'); term.onScroll((ydisp) => { console.log('Scroll position:', ydisp); }); // Connect to PTY server - terminal is ready immediately after open() console.log('\['Demo\] Terminal ready, connecting with size:', term.cols, 'x', term.rows); connectWebSocket(); } // Initialize on load initTerminal(); ``` -------------------------------- ### Initialize Terminal and Write Content Source: https://context7.com/coder/ghostty-web/llms.txt Initializes a new terminal instance and writes initial content to it. Ensure the DOM element for the terminal is available. ```javascript import { init, Terminal } from 'ghostty-web'; await init(); const term = new Terminal(); term.open(document.getElementById('terminal')); term.write('Hello World\r\nLine 2\r\nLine 3'); ``` -------------------------------- ### Initialize and Configure Terminal Source: https://github.com/coder/ghostty-web/blob/main/demo/colors-demo.html Initializes the WASM terminal instance with a custom dark theme and adds the FitAddon for responsive resizing. ```typescript import { init, Terminal } from '../lib/index.ts'; import { FitAddon } from '../lib/addons/fit.ts'; let term; let fitAddon; // ========================================================================= // Initialization // ========================================================================= async function initApp() { try { // Initialize WASM await init(); // Create terminal with dark theme term = new Terminal({ cols: 120, rows: 40, theme: { background: '#1e1e1e', foreground: '#d4d4d4', cursor: '#ffffff', black: '#000000', red: '#cd3131', green: '#0dbc79', yellow: '#e5e510', blue: '#2472c8', magenta: '#bc3fbc', cyan: '#11a8cd', white: '#e5e5e5', brightBlack: '#666666', brightRed: '#f14c4c', brightGreen: '#23d18b', brightYellow: '#f5f543', brightBlue: '#3b8eea', brightMagenta: '#d670d6', brightCyan: '#29b8db', brightWhite: '#ffffff', }, fontFamily: "'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace", fontSize: 14, scrollback: 10000, }); // Add FitAddon fitAddon = new FitAddon(); term.loadAddon(fitAddon); // Open terminal const container = document.getElementById('terminal-container'); term.open(container); fitAddon.fit(); // Handle resize window.addEventListener('resize', () => { fitAddon.fit(); }); // Show welcome message showWelcome(); } catch (error) { console.error('Failed to initialize terminal:', error); } } // ========================================================================= // Helper Functions // ========================================================================= function writeln(text) { term.write(text + '\r\n'); } function clearTerminal() { term.clear(); } // ========================================================================= // Demo Functions // ========================================================================= function showWelcome() { term.clear(); writeln( '\x1b[1;36m╔════════════════════════════════════════════════════════════════════════════════╗\x1b[0m' ); writeln( '\x1b[1;36m║\x1b[0m \x1b[1;35m🎨 ANSI Color Capabilities Demo\x1b[0m \x1b[1;36m║\x1b[0m' ); writeln( '\x1b[1;36m╚════════════════════════════════════════════════════════════════════════════════╝\x1b[0m' ); writeln(''); writeln('This terminal supports:'); writeln(' • 16 standard ANSI colors (8 normal + 8 bright)'); writeln(' • 256-color palette (xterm colors)'); writeln(' • RGB true color (24-bit)'); writeln(' • Text styles: bold, italic, underline, dim, inverse, strikethrough'); writeln(''); writeln('\x1b[1;33mClick the buttons above to explore different color modes!\x1b[0m'); writeln(''); } window.showStandardColors = function () { term.clear(); writeln('\x1b[1;36m═══ Standard 16 ANSI Colors ═══\x1b[0m'); writeln(''); // Foreground colors (30-37) writeln('\x1b[1mForeground Colors (Normal):\x1b[0m'); writeln( '\x1b[30m■\x1b[0m Black (30) \x1b[31m■\x1b[0m Red (31) \x1b[32m■\x1b[0m Green (32) \x1b[33m■\x1b[0m Yellow (33)' ); } ``` -------------------------------- ### Get Fresh WASM Memory Buffer Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Always retrieve a fresh buffer from the WASM memory to avoid issues with detached buffers when memory grows. Use a getter function to ensure the latest buffer is accessed. ```typescript // ❌ WRONG - buffer may become invalid const buffer = this.memory.buffer; // ... time passes, memory grows ... const view = new Uint8Array(buffer); // May be detached! // ✅ CORRECT - get fresh buffer each time private getBuffer(): ArrayBuffer { return this.memory.buffer; } const view = new Uint8Array(this.getBuffer(), ptr, size); ``` -------------------------------- ### Initialize ghostty-web and Create Terminal Source: https://context7.com/coder/ghostty-web/llms.txt Call `init()` before creating any Terminal instances to load the WASM module. Terminals can be created with default or custom options, including dimensions, font, cursor style, and theme. ```javascript import { init, Terminal } from 'ghostty-web'; // Initialize the WASM module (required before creating terminals) await init(); // Create a terminal with default options (80x24) const term = new Terminal(); // Create a terminal with custom options const customTerm = new Terminal({ cols: 120, rows: 40, fontSize: 14, fontFamily: 'Monaco, Menlo, "Courier New", monospace', cursorBlink: true, cursorStyle: 'block', // 'block' | 'underline' | 'bar' scrollback: 10000, theme: { background: '#1a1b26', foreground: '#a9b1d6', cursor: '#ffffff', black: '#000000', red: '#cd3131', green: '#0dbc79', yellow: '#e5e510', blue: '#2472c8', magenta: '#bc3fbc', cyan: '#11a8cd', white: '#e5e5e5', brightBlack: '#666666', brightRed: '#f14c4c', brightGreen: '#23d18b', brightYellow: '#f5f543', brightBlue: '#3b8eea', brightMagenta: '#d670d6', brightCyan: '#29b8db', brightWhite: '#ffffff', }, convertEol: false, // Convert \n to \r\n disableStdin: false, // Disable keyboard input smoothScrollDuration: 100, // Smooth scroll animation in ms (0 = instant) }); ``` -------------------------------- ### Initialize Terminal with FitAddon Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Ensures the terminal container is properly sized upon initialization and window resize events. ```typescript // After opening terminal, must call fit const fitAddon = new FitAddon(); term.loadAddon(fitAddon); await term.open(container); fitAddon.fit(); // ⚠️ Required! Otherwise terminal may not render // On window resize window.addEventListener('resize', () => fitAddon.fit()); ``` -------------------------------- ### Run All Tests Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Execute the entire test suite for the Ghostty WASM Terminal project. ```bash bun test ``` -------------------------------- ### Initialize Terminal and WebSocket Connection Source: https://context7.com/coder/ghostty-web/llms.txt Initializes the ghostty-web terminal instance and establishes a bidirectional WebSocket connection for PTY communication. ```javascript import { init, Terminal, FitAddon } from 'ghostty-web'; async function createTerminal() { await init(); const term = new Terminal({ cursorBlink: true, fontSize: 14, fontFamily: 'Monaco, Menlo, "Courier New", monospace', theme: { background: '#1e1e1e', foreground: '#d4d4d4', }, scrollback: 10000, }); const fitAddon = new FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById('terminal')); fitAddon.fit(); fitAddon.observeResize(); // Connect to PTY server const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${protocol}//${window.location.host}/ws?cols=${term.cols}&rows=${term.rows}`); ws.onopen = () => { console.log('Connected'); term.focus(); }; ws.onmessage = (event) => { term.write(event.data); }; ws.onclose = () => { term.write('\r\n\x1b[31mDisconnected\x1b[0m\r\n'); }; // Send user input to server term.onData((data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(data); } }); // Send resize events to server term.onResize(({ cols, rows }) => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'resize', cols, rows })); } }); return { term, ws, fitAddon }; } createTerminal(); ``` -------------------------------- ### Register Custom Link Provider Source: https://context7.com/coder/ghostty-web/llms.txt Demonstrates how to register a custom link provider to detect and handle specific patterns within terminal lines, such as issue references. ```javascript import { init, Terminal, OSC8LinkProvider, UrlRegexProvider } from 'ghostty-web'; await init(); const term = new Terminal(); term.open(document.getElementById('terminal')); // Built-in providers are registered automatically: // - OSC8LinkProvider for explicit hyperlinks // - UrlRegexProvider for plain text URLs // Write OSC 8 hyperlink term.write('\x1b]8;;https://example.com\x07Click Here\x1b]8;\x07\r\n'); // Write plain URL (auto-detected) term.write('Visit https://github.com/coder/ghostty-web\r\n'); // Register custom link provider term.registerLinkProvider({ provideLinks(y, callback) { const line = term.buffer.active.getLine(y); if (!line) { callback(undefined); return; } const text = line.translateToString(); const links = []; // Match custom pattern (e.g., issue references) const issueRegex = /#(\d+)/g; let match; while ((match = issueRegex.exec(text)) !== null) { links.push({ text: match[0], range: { start: { x: match.index, y }, end: { x: match.index + match[0].length - 1, y }, }, activate: (event) => { if (event.ctrlKey || event.metaKey) { window.open(`https://github.com/issues/${match[1]}`, '_blank'); } }, hover: (isHovered) => { console.log(isHovered ? 'Hovering issue' : 'Left issue'); }, }); } callback(links.length > 0 ? links : undefined); }, }); ``` -------------------------------- ### Run Full CI Checks Before Committing Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Execute all continuous integration checks, including formatting, linting, type checking, testing, and building, before committing code. ```bash bun run fmt && bun run lint && bun run typecheck && bun run test && bun run build ``` -------------------------------- ### Initialize Ghostty Terminal with Scrollbar Source: https://github.com/coder/ghostty-web/blob/main/demo/scrollbar-test.html Initializes the terminal with specific font settings and a scrollback buffer, then populates it with test lines to enable scrolling. ```typescript import { init, Terminal } from '../lib/index.ts'; import { FitAddon } from '../lib/addons/fit.ts'; await init(); const term = new Terminal({ fontSize: 14, fontFamily: 'Monaco, Menlo, monospace', scrollback: 5000, }); const fitAddon = new FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById('terminal')); fitAddon.fit(); // Write lots of lines to create scrollback term.write('\\x1b[1;32m=== Scrollbar Test ===\\x1b[0m\\r\\n\\r\\n'); term.write('Generating 200 lines of content...\\r\\n\\r\\n'); for (let i = 1; i <= 200; i++) { const color = 31 + (i % 7); term.write( `\\x1b[${color}mLine ${i}: This is test content for scrolling. The quick brown fox jumps over the lazy dog.\\x1b[0m\\r\\n` ); } term.write('\\r\\n\\x1b[1;33m✅ Done! Now try:\\x1b[0m\\r\\n'); term.write(' • Scroll up with mouse wheel\\r\\n'); term.write(' • Click on the scrollbar track to jump\\r\\n'); term.write(' • Drag the scrollbar thumb\\r\\n'); term.write(' • Notice: NO blue banner! 🎉\\r\\n'); // Handle window resize window.addEventListener('resize', () => { fitAddon.fit(); }); ``` -------------------------------- ### Initialize Terminal with Scrollback Source: https://context7.com/coder/ghostty-web/llms.txt Initializes the terminal with custom scrollback and smooth scrolling settings. Ensure the DOM element exists before opening the terminal. ```javascript import { init, Terminal } from 'ghostty-web'; await init(); const term = new Terminal({ scrollback: 10000, // Keep 10,000 lines of history smoothScrollDuration: 100, // Smooth scroll animation }); term.open(document.getElementById('terminal')); ``` -------------------------------- ### Run Linter with Biome Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Execute the linter using Biome to check for code quality and potential issues. ```bash bun run lint ``` -------------------------------- ### Terminal Resizing with FitAddon Source: https://context7.com/coder/ghostty-web/llms.txt Programmatically resize the terminal or use the FitAddon for automatic resizing to fit its container. The FitAddon can also propose dimensions without applying them and observe container resize events. ```javascript import { init, Terminal, FitAddon } from 'ghostty-web'; await init(); const term = new Terminal(); term.open(document.getElementById('terminal')); // Manual resize term.resize(100, 30); // Get current dimensions console.log(`Size: ${term.cols}x${term.rows}`); // Use FitAddon for automatic resizing const fitAddon = new FitAddon(); term.loadAddon(fitAddon); // Fit terminal to container once fitAddon.fit(); // Get proposed dimensions without applying const dims = fitAddon.proposeDimensions(); console.log(`Proposed: ${dims?.cols}x${dims?.rows}`); // Auto-fit on container resize (uses ResizeObserver) fitAddon.observeResize(); // Also handle window resize events window.addEventListener('resize', () => { fitAddon.fit(); }); ``` -------------------------------- ### Format Code with Prettier Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Check code formatting using Prettier. Use `bun run fmt:fix` to automatically fix formatting issues. ```bash bun run fmt ``` -------------------------------- ### Basic CSS for Terminal Window Source: https://github.com/coder/ghostty-web/blob/main/demo/index.html Provides essential CSS for styling the terminal window, including layout, background gradients, typography, and the title bar with traffic lights. It also includes media queries for responsiveness. ```css ghostty-web * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 40px 20px; } .terminal-window { width: 100%; max-width: 1000px; background: #1e1e1e; border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); overflow: hidden; } .title-bar { background: #2d2d2d; padding: 12px 16px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #1a1a1a; } .traffic-lights { display: flex; gap: 8px; } .light { width: 12px; height: 12px; border-radius: 50%; } .light.red { background: #ff5f56; } .light.yellow { background: #ffbd2e; } .light.green { background: #27c93f; } .title { color: #e5e5e5; font-size: 13px; font-weight: 500; letter-spacing: 0.3px; } .connection-status { margin-left: auto; font-size: 11px; color: #888; display: flex; align-items: center; gap: 6px; } .connection-dot { width: 6px; height: 6px; border-radius: 50%; background: #666; } .connection-dot.connected { background: #27c93f; } #terminal-container { height: 600px; padding: 16px; background: #1e1e1e; position: relative; overflow: hidden; } /* Ensure terminal canvas can handle scrolling */ #terminal-container canvas { display: block; } @media (max-width: 768px) { body { padding: 20px 10px; } #terminal-container { height: 500px; } } ``` -------------------------------- ### Create a Custom Terminal Addon Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Implement the ITerminalAddon interface to create reusable terminal extensions. The activate method initializes the addon with the terminal instance, and dispose handles cleanup. ```typescript export class MyAddon implements ITerminalAddon { private terminal?: Terminal; activate(terminal: Terminal): void { this.terminal = terminal; // Initialize addon } dispose(): void { // Cleanup } } ``` -------------------------------- ### Configure Nginx for Reverse Proxy Source: https://github.com/coder/ghostty-web/blob/main/demo/README.md This Nginx configuration allows the demo server to be accessed via a domain name, handling HTTP and WebSocket traffic through a reverse proxy. It sets necessary headers for proper proxying. ```nginx server { listen 80; server_name example.com; location / { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` -------------------------------- ### Listen for Buffer Change Events Source: https://context7.com/coder/ghostty-web/llms.txt Registers a callback to be notified whenever the terminal switches between its normal and alternate screen buffers. ```javascript // Listen for buffer changes (switching between normal/alternate) term.buffer.onBufferChange((newBuffer) => { console.log('Switched to:', newBuffer.type); }); ``` -------------------------------- ### Manage Terminal Addons Source: https://context7.com/coder/ghostty-web/llms.txt Extend terminal functionality by loading built-in or custom addons. Addons are automatically disposed when the terminal is disposed. ```javascript import { init, Terminal, FitAddon } from 'ghostty-web'; await init(); const term = new Terminal(); // Create and load addon before or after open() const fitAddon = new FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById('terminal')); // Use addon functionality fitAddon.fit(); fitAddon.observeResize(); // Addons are automatically disposed when terminal is disposed term.dispose(); // Create custom addon class MyAddon { activate(terminal) { this.terminal = terminal; this.disposable = terminal.onData((data) => { console.log('Input:', data); }); } dispose() { this.disposable?.dispose(); } } const myAddon = new MyAddon(); term.loadAddon(myAddon); ``` -------------------------------- ### Establish WebSocket Connection for Terminal Source: https://github.com/coder/ghostty-web/blob/main/demo/index.html Establishes a WebSocket connection to the PTY server, dynamically constructing the URL based on the current protocol and host. It includes handlers for connection open, message reception, errors, and closure, with an automatic reconnection mechanism. ```typescript function connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws?cols=${term.cols}&rows=${term.rows}`; ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('WebSocket connected'); updateConnectionStatus(true); }; ws.onmessage = (event) => { // Server sends raw strings, not JSON term.write(event.data); }; ws.onerror = (error) => { console.error('WebSocket error:', error); updateConnectionStatus(false); }; ws.onclose = () => { console.log('WebSocket disconnected'); updateConnectionStatus(false); // Attempt to reconnect after 3 seconds setTimeout(() => { if (!ws || ws.readyState === WebSocket.CLOSED) { connectWebSocket(); } }, 3000); }; } ``` -------------------------------- ### Run Specific Test File Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Execute tests only for a specific file, such as the terminal implementation. ```bash bun test lib/terminal.test.ts ``` -------------------------------- ### Run Tests in Watch Mode Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Continuously run tests and re-execute them automatically when file changes are detected. Note: May require manual restart with Ctrl+C. ```bash bun test --watch ``` -------------------------------- ### Link Detection and Hyperlinks API Source: https://context7.com/coder/ghostty-web/llms.txt Register custom link providers and handle OSC 8 hyperlinks for interactive links. ```APIDOC ## Link Detection and Hyperlinks Register custom link providers for URL detection and OSC 8 hyperlinks. ### Method Terminal Initialization and Link Provider Registration ### Endpoint N/A (Client-side API) ### Built-in Providers - **OSC8LinkProvider**: Automatically handles explicit OSC 8 hyperlink sequences. - **UrlRegexProvider**: Automatically detects and links plain text URLs. ### Registering Custom Link Providers Use the `registerLinkProvider` method on the `Terminal` instance. ### `registerLinkProvider` Method - **provider**: An object with a `provideLinks` method. - **provideLinks(y: number, callback: (links?: Link[]) => void)**: This method is called for each line to detect links. It receives the line number `y` and a callback function. The callback should be invoked with an array of `Link` objects if links are found, or `undefined` otherwise. ### Link Object Structure - **text**: (string) - The text of the link. - **range**: { start: { x: number, y: number }, end: { x: number, y: number } } - The character range of the link within the buffer. - **activate**: (event: MouseEvent) => void - A function called when the link is activated (e.g., clicked). Can be used to open URLs or perform other actions. - **hover**: (isHovered: boolean) => void - A function called when the mouse pointer enters or leaves the link area. ### Request Example ```javascript import { init, Terminal, OSC8LinkProvider, UrlRegexProvider } from 'ghostty-web'; await init(); const term = new Terminal(); term.open(document.getElementById('terminal')); // Write OSC 8 hyperlink term.write('\x1b]8;;https://example.com\x07Click Here\x1b]8;;\x07\r\n'); // Write plain URL (auto-detected) term.write('Visit https://github.com/coder/ghostty-web\r\n'); // Register custom link provider term.registerLinkProvider({ provideLinks(y, callback) { const line = term.buffer.active.getLine(y); if (!line) { callback(undefined); return; } const text = line.translateToString(); const links = []; // Match custom pattern (e.g., issue references) const issueRegex = /#(\d+)/g; let match; while ((match = issueRegex.exec(text)) !== null) { links.push({ text: match[0], range: { start: { x: match.index, y }, end: { x: match.index + match[0].length - 1, y }, }, activate: (event) => { if (event.ctrlKey || event.metaKey) { window.open(`https://github.com/issues/${match[1]}`, '_blank'); } }, hover: (isHovered) => { console.log(isHovered ? 'Hovering issue' : 'Left issue'); }, }); } callback(links.length > 0 ? links : undefined); }, }); ``` ``` -------------------------------- ### Write Data to ghostty-web Terminal Source: https://context7.com/coder/ghostty-web/llms.txt Use `write()` and `writeln()` to send data, including plain text, ANSI color codes, and binary data. `clear()` and `reset()` can be used to clear the screen or perform a full terminal reset. ```javascript import { init, Terminal } from 'ghostty-web'; await init(); const term = new Terminal(); term.open(document.getElementById('terminal')); // Write plain text term.write('Hello, Terminal!\r\n'); // Write with ANSI colors term.write('\x1b[32mGreen text\x1b[0m\r\n'); term.write('\x1b[1;31mBold red text\x1b[0m\r\n'); term.write('\x1b[4;34mUnderlined blue text\x1b[0m\r\n'); // Write with callback (called after next render) term.write('Loading...', () => { console.log('Text rendered'); }); // Write line with automatic \r\n term.writeln('This line ends automatically'); // Write binary data (Uint8Array) const data = new TextEncoder().encode('Binary data\r\n'); term.write(data); // Clear screen and reset cursor term.clear(); // Full terminal reset term.reset(); ``` -------------------------------- ### Perform TypeScript Type Checking Source: https://github.com/coder/ghostty-web/blob/main/AGENTS.md Run the TypeScript compiler to perform static type checking on the project. ```bash bun run typecheck ``` -------------------------------- ### Terminal Styling and Color Combinations Source: https://github.com/coder/ghostty-web/blob/main/demo/colors-demo.html Functions demonstrating various text styles, color combinations, and UI components like buttons and progress bars using ANSI escape codes. ```javascript text (SGR 3)\x1b[0m'); writeln('\x1b[4mUnderline text (SGR 4)\x1b[0m'); writeln('\x1b[7mInverse/Reverse video (SGR 7)\x1b[0m'); writeln('\x1b[9mStrikethrough text (SGR 9)\x1b[0m'); writeln(''); writeln('\x1b[1mCombined Styles with Colors:\x1b[0m'); writeln('\x1b[1;31mBold Red\x1b[0m'); writeln('\x1b[1;4;32mBold Underlined Green\x1b[0m'); writeln('\x1b[3;36mItalic Cyan\x1b[0m'); writeln('\x1b[1;3;4;35mBold Italic Underlined Magenta\x1b[0m'); writeln('\x1b[9;90mStrikethrough Gray\x1b[0m'); writeln(''); writeln('\x1b[1mWith Backgrounds:\x1b[0m'); writeln('\x1b[1;37;44mBold white on blue\x1b[0m'); writeln('\x1b[4;33;41mUnderlined yellow on red\x1b[0m'); writeln('\x1b[7;32mInverse green\x1b[0m (swaps fg/bg)'); writeln(''); writeln('\x1b[1mDemonstration:\x1b[0m'); writeln( 'Normal → \x1b[1mBold\x1b[0m → \x1b[2mDim\x1b[0m → \x1b[3mItalic\x1b[0m → \x1b[4mUnderline\x1b[0m → Normal' ); writeln(''); }; window.showCombinations = function () { term.clear(); writeln('\x1b[1;36m═══ Style & Color Combinations ═══\x1b[0m'); writeln(''); writeln('\x1b[1mError/Warning Styles:\x1b[0m'); writeln('\x1b[1;31m[ERROR]\x1b[0m Something went wrong!'); writeln('\x1b[1;33m[WARNING]\x1b[0m This is a warning message'); writeln('\x1b[1;32m[SUCCESS]\x1b[0m Operation completed successfully'); writeln('\x1b[1;36m[INFO]\x1b[0m Informational message'); writeln(''); writeln('\x1b[1mSyntax Highlighting Example:\x1b[0m'); writeln('\x1b[35mfunction\x1b[0m \x1b[33mhelloWorld\x1b[0m() {'); writeln(' \x1b[34mconst\x1b[0m \x1b[36mmessage\x1b[0m = \x1b[32m"Hello, World!"\x1b[0m;'); writeln(' \x1b[36mconsole\x1b[0m.\x1b[33mlog\x1b[0m(\x1b[36mmessage\x1b[0m);'); writeln(' \x1b[35mreturn\x1b[0m \x1b[34mtrue\x1b[0m;'); writeln('}'); writeln(''); writeln('\x1b[1mUI Elements:\x1b[0m'); writeln('\x1b[1;37;44m PRIMARY BUTTON \x1b[0m'); writeln('\x1b[1;30;47m SECONDARY BUTTON \x1b[0m'); writeln('\x1b[1;37;42m SUCCESS BUTTON \x1b[0m'); writeln('\x1b[1;37;41m DANGER BUTTON \x1b[0m'); writeln(''); writeln('\x1b[1mProgress Indicators:\x1b[0m'); writeln('[\x1b[1;32m████████████\x1b[0m\x1b[2m············\x1b[0m] 50%'); writeln('[\x1b[1;36m████████████████████\x1b[0m\x1b[2m····\x1b[0m] 80%'); writeln('[\x1b[1;32m████████████████████████\x1b[0m] 100% Complete!'); writeln(''); writeln('\x1b[1mTables with Colors:\x1b[0m'); writeln('╔═══════════╦═══════╦═══════╗'); writeln('║ \x1b[1mName\x1b[0m ║ \x1b[1mStatus\x1b[0m║ \x1b[1mValue\x1b[0m ║'); writeln('╠═══════════╬═══════╬═══════╣'); writeln('║ Service A ║ \x1b[1;32m ON\x1b[0m ║ \x1b[36m100\x1b[0m ║'); writeln('║ Service B ║ \x1b[1;31m OFF\x1b[0m ║ \x1b[36m0\x1b[0m ║'); writeln('║ Service C ║ \x1b[1;33m WARN\x1b[0m ║ \x1b[36m50\x1b[0m ║'); writeln('╚═══════════╩═══════╩═══════╝'); writeln(''); }; window.showAll = function () { showStandardColors(); writeln(''); writeln('\x1b[1;35m' + '═'.repeat(80) + '\x1b[0m'); writeln(''); // Add 256 colors (abbreviated) writeln('\x1b[1;36m═══ 256-Color Palette (Sample) ═══\x1b[0m'); writeln(''); let line = ''; for (let i = 0; i < 256; i++) { line += `\x1b[48;5;${i}m \x1b[0m`; if ((i + 1) % 32 === 0) { writeln(line); line = ''; } } writeln(''); writeln('\x1b[1;35m' + '═'.repeat(80) + '\x1b[0m'); writeln(''); // RGB samples writeln('\x1b[1;36m═══ RGB True Color (Samples) ═══\x1b[0m'); writeln(''); writeln( '\x1b[38;2;255;105;180m█\x1b[0m Hot Pink \x1b[38;2;64;224;208m█\x1b[0m Turquoise \x1b[38;2;255;215;0m█\x1b[0m Gold \x1b[38;2;138;43;226m█\x1b[0m Blue Violet' ); writeln(''); writeln('\x1b[1;35m' + '═'.repeat(80) + '\x1b[0m'); writeln(''); showTextStyles(); }; // Make functions globally available window.clearTerminal = clearTerminal; // Initialize on page load initApp(); ```