#!/usr/bin/env -S node --import tsx import 'dotenv/config'; import React from 'react'; import { render } from 'ink'; import InkApp from './src/ui/InkApp.jsx'; import terminalService from './src/terminalService.js'; // Start the PTY backend independent from UI lifecycle terminalService.start(); const { unmount } = render(React.createElement(InkApp)); // ESC to exit (only bare ESC, not escape sequences like arrows) if (process.stdin.isTTY) { try { process.stdin.setRawMode(true); } catch { } let escPending = false; let escTimer = null; const exitCleanly = () => { unmount(); try { terminalService.dispose(); } catch { } try { process.stdin.setRawMode(false); } catch { } process.exit(0); }; const onData = (data) => { const buffer = Buffer.isBuffer(data) ? data : Buffer.from(String(data)); for (const byte of buffer) { // Ctrl-C (ETX) if (byte === 0x03) { return exitCleanly(); } if (!escPending) { if (byte === 0x1b) { // ESC escPending = true; escTimer = setTimeout(() => { // No additional byte followed: treat as bare ESC escPending = false; escTimer = null; exitCleanly(); }, 120); } // else: ignore other bytes } else { // Some byte followed ESC quickly: it's an escape sequence → cancel exit if (escTimer) { clearTimeout(escTimer); escTimer = null; } escPending = false; // Do not process further for exit } } }; process.stdin.on('data', onData); // Also handle SIGINT in case raw mode changes or comes from elsewhere const onSigint = () => exitCleanly(); process.on('SIGINT', onSigint); }