#!/usr/bin/env -S node --max-old-space-size=8192 --expose-gc // Must be first import. If the user explicitly opts into truecolor, this // nudges chalk / supports-color before either package is initialized. import './lib/forceTruecolor.js' import type { FrameEvent } from '@hermes/ink' import { GatewayClient } from './gatewayClient.js' import { setupGracefulExit } from './lib/gracefulExit.js' import { formatBytes, type HeapDumpResult, performHeapDump } from './lib/memory.js' import { type MemorySnapshot, startMemoryMonitor } from './lib/memoryMonitor.js' import { resetTerminalModes } from './lib/terminalModes.js' if (!process.stdin.isTTY) { console.log('hermes-tui: no TTY') process.exit(0) } // Start from a clean slate. If a previous TUI crashed or was kill -9'd, the // terminal tab can still have mouse/focus/paste modes enabled. resetTerminalModes() const gw = new GatewayClient() gw.start() const dumpNotice = (snap: MemorySnapshot, dump: HeapDumpResult | null) => `hermes-tui: ${snap.level} memory (${formatBytes(snap.heapUsed)}) — auto heap dump → ${dump?.heapPath ?? '(failed)'}\n` setupGracefulExit({ cleanups: [ () => { resetTerminalModes() return gw.kill() } ], onError: (scope, err) => { const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err) process.stderr.write(`hermes-tui ${scope}: ${message.slice(0, 2000)}\n`) }, onSignal: signal => { resetTerminalModes() process.stderr.write(`hermes-tui: received ${signal}\n`) } }) const stopMemoryMonitor = startMemoryMonitor({ onCritical: (snap, dump) => { resetTerminalModes() process.stderr.write(dumpNotice(snap, dump)) process.stderr.write('hermes-tui: exiting to avoid OOM; restart to recover\n') process.exit(137) }, onHigh: (snap, dump) => process.stderr.write(dumpNotice(snap, dump)) }) if (process.env.HERMES_HEAPDUMP_ON_START === '1') { void performHeapDump('manual') } process.on('beforeExit', () => stopMemoryMonitor()) const [ink, { App }, { logFrameEvent }, { trackFrame }] = await Promise.all([ import('@hermes/ink'), import('./app.js'), import('./lib/perfPane.js'), import('./lib/fpsStore.js') ]) // Both consumers are undefined when their env flags are off; only attach // onFrame when at least one is on so ink skips timing in the default case. const onFrame = logFrameEvent || trackFrame ? (event: FrameEvent) => { logFrameEvent?.(event) trackFrame?.(event.durationMs) } : undefined ink.render(, { exitOnCtrlC: false, onFrame })