Files
hermes-agent/ui-tui/src/entry.tsx
brooklyn! 6b09df39be fix(tui): restore macOS copy behavior and theme polish (#17131)
This PR groups the TUI fixes that restore macOS Terminal usability and clean up the theme/composer regressions:

- copy transcript selections on macOS drag-release so Terminal.app users can copy while mouse tracking is enabled
- copy composer selections on macOS drag-release; composer selection is internal to TextInput and does not use the global Ink selection bus
- keep IDE Cmd+C forwarding setup macOS-only, and make keybinding conflict checks respect simple when-clause overlap/negation
- force truecolor before chalk initializes (unless NO_COLOR / FORCE_COLOR / HERMES_TUI_TRUECOLOR opt-outs apply) so the default banner keeps its gold/amber/bronze gradient in Terminal.app
- move TUI surfaces onto semantic theme tokens and preserve skin prompt symbols as bare tokens with renderer-owned spacing
- render focused placeholders as dim hint text in TTY mode instead of inverse/selected-looking synthetic cursor text
2026-04-28 18:47:14 -05:00

70 lines
2.3 KiB
JavaScript

#!/usr/bin/env -S node --max-old-space-size=8192 --expose-gc
// Must be first import — mutates process.env.FORCE_COLOR / COLORTERM before
// any chalk / supports-color import so the banner gradient renders in
// truecolor instead of being downsampled to 256-color (which collapses
// gold #FFD700 and amber #FFBF00 to the same slot).
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'
if (!process.stdin.isTTY) {
console.log('hermes-tui: no TTY')
process.exit(0)
}
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: [() => 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 => process.stderr.write(`hermes-tui: received ${signal}\n`)
})
const stopMemoryMonitor = startMemoryMonitor({
onCritical: (snap, dump) => {
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(<App gw={gw} />, { exitOnCtrlC: false, onFrame })