61 lines
1.9 KiB
JavaScript
Executable File
61 lines
1.9 KiB
JavaScript
Executable File
#!/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);
|
|
}
|
|
|
|
|