diff --git a/ui-tui-opentui-v2/src/entry/main.tsx b/ui-tui-opentui-v2/src/entry/main.tsx index c1a30d53d7c..f0163fc3d97 100644 --- a/ui-tui-opentui-v2/src/entry/main.tsx +++ b/ui-tui-opentui-v2/src/entry/main.tsx @@ -32,6 +32,7 @@ import { getLog } from '../boundary/log.ts' import { acquireRenderer } from '../boundary/renderer.ts' import { makeAppLayer } from '../boundary/runtime.ts' import { nthAssistantResponse } from '../logic/copy.ts' +import { envFlag } from '../logic/env.ts' import { createPromptHistory, dirHistoryPersister, loadDirHistory } from '../logic/history.ts' import { createPasteStore } from '../logic/pastes.ts' import { mapResumeHistory, mapSessionList } from '../logic/resume.ts' @@ -461,16 +462,14 @@ function streamHello(controller: FakeGatewayController): void { controller.emit({ type: 'message.complete' }) } -const TRUE_RE = /^(?:1|true|yes|on)$/i - if (import.meta.main) { - const fake = TRUE_RE.test(process.env.HERMES_TUI_FAKE?.trim() ?? '') + const fake = envFlag(process.env.HERMES_TUI_FAKE, false) const cols = process.stdout.columns || 80 const initialPrompt = process.env.HERMES_TUI_PROMPT?.trim() || process.argv.slice(2).join(' ').trim() const resumeId = process.env.HERMES_TUI_RESUME?.trim() // Mouse on by default (opencode parity: wheel-scroll the transcript, drag the // scrollbar, click-to-expand tools, text-aware selection). HERMES_TUI_MOUSE=0 opts out. - const mouse = !/^(?:0|false|no|off)$/i.test(process.env.HERMES_TUI_MOUSE?.trim() ?? '') + const mouse = envFlag(process.env.HERMES_TUI_MOUSE, true) const base = { mouse, fake, cols } const withPrompt = initialPrompt ? { ...base, initialPrompt } : base const input: TuiInput = resumeId ? { ...withPrompt, resumeId } : withPrompt diff --git a/ui-tui-opentui-v2/src/logic/env.ts b/ui-tui-opentui-v2/src/logic/env.ts new file mode 100644 index 00000000000..294e5c69ef2 --- /dev/null +++ b/ui-tui-opentui-v2/src/logic/env.ts @@ -0,0 +1,16 @@ +/** + * env — shared boolean env-flag parsing (one source for the TRUE/FALSE regexes). + * + * Recognized truthy values: 1/true/yes/on; falsy: 0/false/no/off (case-insensitive, + * surrounding whitespace trimmed). Anything else (incl. unset) is "unrecognized". + */ +export const TRUE_RE = /^(?:1|true|yes|on)$/i +export const FALSE_RE = /^(?:0|false|no|off)$/i + +/** Parse a boolean env var; returns `fallback` when unset/unrecognized. */ +export function envFlag(value: string | undefined, fallback: boolean): boolean { + const v = value?.trim() ?? '' + if (TRUE_RE.test(v)) return true + if (FALSE_RE.test(v)) return false + return fallback +} diff --git a/ui-tui-opentui-v2/src/logic/theme.ts b/ui-tui-opentui-v2/src/logic/theme.ts index e273a98f345..7d20013b8e2 100644 --- a/ui-tui-opentui-v2/src/logic/theme.ts +++ b/ui-tui-opentui-v2/src/logic/theme.ts @@ -14,6 +14,8 @@ * ui-tui/src/gatewayTypes.ts). Keep this port in sync if that contract changes. */ +import { FALSE_RE, TRUE_RE } from './env.ts' + export interface ThemeColors { primary: string accent: string @@ -317,9 +319,6 @@ export const LIGHT_THEME: Theme = { bannerHero: '' } -const TRUE_RE = /^(?:1|true|yes|on)$/ -const FALSE_RE = /^(?:0|false|no|off)$/ - const LIGHT_DEFAULT_TERM_PROGRAMS = new Set(['Apple_Terminal']) const LUMA_LIGHT_THRESHOLD = 0.6 diff --git a/ui-tui-opentui-v2/src/test/env.test.ts b/ui-tui-opentui-v2/src/test/env.test.ts new file mode 100644 index 00000000000..cf510b8ae76 --- /dev/null +++ b/ui-tui-opentui-v2/src/test/env.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from 'bun:test' + +import { envFlag } from '../logic/env.ts' + +describe('envFlag', () => { + test('recognizes truthy values regardless of case/whitespace', () => { + for (const v of ['1', 'true', 'yes', 'on', 'TRUE', 'Yes', ' on ']) { + expect(envFlag(v, false)).toBe(true) + } + }) + + test('recognizes falsy values regardless of case/whitespace', () => { + for (const v of ['0', 'false', 'no', 'off', 'FALSE', 'No', ' off ']) { + expect(envFlag(v, true)).toBe(false) + } + }) + + test('returns fallback when unset', () => { + expect(envFlag(undefined, true)).toBe(true) + expect(envFlag(undefined, false)).toBe(false) + expect(envFlag('', true)).toBe(true) + expect(envFlag(' ', false)).toBe(false) + }) + + test('returns fallback for unrecognized garbage', () => { + expect(envFlag('maybe', true)).toBe(true) + expect(envFlag('maybe', false)).toBe(false) + expect(envFlag('2', true)).toBe(true) + expect(envFlag('enabled', false)).toBe(false) + }) +})