2026-04-02 19:06:42 -05:00
|
|
|
|
export interface ThemeColors {
|
2026-04-02 19:34:30 -05:00
|
|
|
|
gold: string
|
|
|
|
|
|
amber: string
|
|
|
|
|
|
bronze: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
cornsilk: string
|
2026-04-02 19:34:30 -05:00
|
|
|
|
dim: string
|
2026-04-14 22:30:18 -05:00
|
|
|
|
completionBg: string
|
|
|
|
|
|
completionCurrentBg: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
2026-04-02 19:34:30 -05:00
|
|
|
|
label: string
|
|
|
|
|
|
ok: string
|
|
|
|
|
|
error: string
|
|
|
|
|
|
warn: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
prompt: string
|
|
|
|
|
|
sessionLabel: string
|
|
|
|
|
|
sessionBorder: string
|
|
|
|
|
|
|
2026-04-02 19:34:30 -05:00
|
|
|
|
statusBg: string
|
|
|
|
|
|
statusFg: string
|
|
|
|
|
|
statusGood: string
|
|
|
|
|
|
statusWarn: string
|
|
|
|
|
|
statusBad: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
statusCritical: string
|
2026-04-16 15:50:28 -05:00
|
|
|
|
selectionBg: string
|
|
|
|
|
|
|
2026-04-08 12:11:55 -05:00
|
|
|
|
diffAdded: string
|
|
|
|
|
|
diffRemoved: string
|
|
|
|
|
|
diffAddedWord: string
|
|
|
|
|
|
diffRemovedWord: string
|
2026-04-15 16:34:58 -05:00
|
|
|
|
|
|
|
|
|
|
shellDollar: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ThemeBrand {
|
2026-04-02 19:34:30 -05:00
|
|
|
|
name: string
|
|
|
|
|
|
icon: string
|
|
|
|
|
|
prompt: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
welcome: string
|
|
|
|
|
|
goodbye: string
|
2026-04-02 19:34:30 -05:00
|
|
|
|
tool: string
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
helpHeader: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Theme {
|
|
|
|
|
|
color: ThemeColors
|
|
|
|
|
|
brand: ThemeBrand
|
2026-04-07 23:59:11 -04:00
|
|
|
|
bannerLogo: string
|
|
|
|
|
|
bannerHero: string
|
2026-04-02 19:06:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-14 22:30:18 -05:00
|
|
|
|
// ── Color math ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function parseHex(h: string): [number, number, number] | null {
|
|
|
|
|
|
const m = /^#?([0-9a-f]{6})$/i.exec(h)
|
|
|
|
|
|
|
|
|
|
|
|
if (!m) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const n = parseInt(m[1]!, 16)
|
|
|
|
|
|
|
|
|
|
|
|
return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function mix(a: string, b: string, t: number) {
|
|
|
|
|
|
const pa = parseHex(a)
|
|
|
|
|
|
const pb = parseHex(b)
|
|
|
|
|
|
|
|
|
|
|
|
if (!pa || !pb) {
|
|
|
|
|
|
return a
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lerp = (i: 0 | 1 | 2) => Math.round(pa[i] + (pb[i] - pa[i]) * t)
|
|
|
|
|
|
|
|
|
|
|
|
return '#' + ((1 << 24) | (lerp(0) << 16) | (lerp(1) << 8) | lerp(2)).toString(16).slice(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Defaults ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-18 17:49:40 -05:00
|
|
|
|
const BRAND: ThemeBrand = {
|
|
|
|
|
|
name: 'Hermes Agent',
|
|
|
|
|
|
icon: '⚕',
|
|
|
|
|
|
prompt: '❯',
|
|
|
|
|
|
welcome: 'Type your message or /help for commands.',
|
|
|
|
|
|
goodbye: 'Goodbye! ⚕',
|
|
|
|
|
|
tool: '┊',
|
|
|
|
|
|
helpHeader: '(^_^)? Commands'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const DARK_THEME: Theme = {
|
2026-04-02 19:06:42 -05:00
|
|
|
|
color: {
|
2026-04-02 19:34:30 -05:00
|
|
|
|
gold: '#FFD700',
|
|
|
|
|
|
amber: '#FFBF00',
|
|
|
|
|
|
bronze: '#CD7F32',
|
2026-04-02 19:06:42 -05:00
|
|
|
|
cornsilk: '#FFF8DC',
|
2026-04-02 19:34:30 -05:00
|
|
|
|
dim: '#B8860B',
|
2026-04-14 22:30:18 -05:00
|
|
|
|
completionBg: '#FFFFFF',
|
|
|
|
|
|
completionCurrentBg: mix('#FFFFFF', '#FFBF00', 0.25),
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
2026-04-14 22:30:18 -05:00
|
|
|
|
label: '#DAA520',
|
2026-04-02 19:34:30 -05:00
|
|
|
|
ok: '#4caf50',
|
|
|
|
|
|
error: '#ef5350',
|
|
|
|
|
|
warn: '#ffa726',
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
prompt: '#FFF8DC',
|
|
|
|
|
|
sessionLabel: '#B8860B',
|
|
|
|
|
|
sessionBorder: '#B8860B',
|
|
|
|
|
|
|
2026-04-02 19:34:30 -05:00
|
|
|
|
statusBg: '#1a1a2e',
|
|
|
|
|
|
statusFg: '#C0C0C0',
|
|
|
|
|
|
statusGood: '#8FBC8F',
|
|
|
|
|
|
statusWarn: '#FFD700',
|
|
|
|
|
|
statusBad: '#FF8C00',
|
2026-04-08 12:11:55 -05:00
|
|
|
|
statusCritical: '#FF6B6B',
|
2026-04-16 15:50:28 -05:00
|
|
|
|
selectionBg: '#3a3a55',
|
|
|
|
|
|
|
2026-04-08 12:11:55 -05:00
|
|
|
|
diffAdded: 'rgb(220,255,220)',
|
|
|
|
|
|
diffRemoved: 'rgb(255,220,220)',
|
|
|
|
|
|
diffAddedWord: 'rgb(36,138,61)',
|
2026-04-15 16:34:58 -05:00
|
|
|
|
diffRemovedWord: 'rgb(207,34,46)',
|
|
|
|
|
|
shellDollar: '#4dabf7'
|
2026-04-02 19:06:42 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 17:49:40 -05:00
|
|
|
|
brand: BRAND,
|
|
|
|
|
|
|
|
|
|
|
|
bannerLogo: '',
|
|
|
|
|
|
bannerHero: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Light-terminal palette: darker golds/ambers that stay legible on white
|
|
|
|
|
|
// backgrounds. Same shape as DARK_THEME so `fromSkin` still layers on top
|
|
|
|
|
|
// cleanly (#11300).
|
|
|
|
|
|
export const LIGHT_THEME: Theme = {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
gold: '#8B6914',
|
|
|
|
|
|
amber: '#A0651C',
|
|
|
|
|
|
bronze: '#7A4F1F',
|
|
|
|
|
|
cornsilk: '#3D2F13',
|
|
|
|
|
|
dim: '#7A5A0F',
|
|
|
|
|
|
completionBg: '#F5F5F5',
|
|
|
|
|
|
completionCurrentBg: mix('#F5F5F5', '#A0651C', 0.25),
|
|
|
|
|
|
|
|
|
|
|
|
label: '#7A5A0F',
|
|
|
|
|
|
ok: '#2E7D32',
|
|
|
|
|
|
error: '#C62828',
|
|
|
|
|
|
warn: '#E65100',
|
|
|
|
|
|
|
|
|
|
|
|
prompt: '#2B2014',
|
|
|
|
|
|
sessionLabel: '#7A5A0F',
|
|
|
|
|
|
sessionBorder: '#7A5A0F',
|
|
|
|
|
|
|
|
|
|
|
|
statusBg: '#F5F5F5',
|
|
|
|
|
|
statusFg: '#333333',
|
|
|
|
|
|
statusGood: '#2E7D32',
|
|
|
|
|
|
statusWarn: '#8B6914',
|
|
|
|
|
|
statusBad: '#D84315',
|
|
|
|
|
|
statusCritical: '#B71C1C',
|
|
|
|
|
|
selectionBg: '#D4E4F7',
|
|
|
|
|
|
|
|
|
|
|
|
diffAdded: 'rgb(200,240,200)',
|
|
|
|
|
|
diffRemoved: 'rgb(240,200,200)',
|
|
|
|
|
|
diffAddedWord: 'rgb(27,94,32)',
|
|
|
|
|
|
diffRemovedWord: 'rgb(183,28,28)',
|
|
|
|
|
|
shellDollar: '#1565C0'
|
2026-04-07 23:59:11 -04:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-18 17:49:40 -05:00
|
|
|
|
brand: BRAND,
|
|
|
|
|
|
|
2026-04-07 23:59:11 -04:00
|
|
|
|
bannerLogo: '',
|
|
|
|
|
|
bannerHero: ''
|
2026-04-02 19:06:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:12:13 -05:00
|
|
|
|
// Pick light vs dark. Explicit `HERMES_TUI_LIGHT` wins; otherwise sniff
|
|
|
|
|
|
// `COLORFGBG` (set by XFCE Terminal, rxvt, Terminal.app, etc.) — last field is the
|
|
|
|
|
|
// background ANSI index; 7/15 are the "white" slots most light themes emit (#11300).
|
|
|
|
|
|
export function detectLightMode(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
|
|
|
|
const explicit = (env.HERMES_TUI_LIGHT ?? '').trim().toLowerCase()
|
|
|
|
|
|
|
|
|
|
|
|
if (/^(?:1|true|yes|on)$/.test(explicit)) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (/^(?:0|false|no|off)$/.test(explicit)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bg = Number((env.COLORFGBG ?? '').trim().split(';').at(-1))
|
|
|
|
|
|
|
|
|
|
|
|
return bg === 7 || bg === 15
|
|
|
|
|
|
}
|
2026-04-18 17:49:40 -05:00
|
|
|
|
|
2026-04-20 11:12:13 -05:00
|
|
|
|
export const DEFAULT_THEME: Theme = detectLightMode() ? LIGHT_THEME : DARK_THEME
|
2026-04-18 17:49:40 -05:00
|
|
|
|
|
2026-04-14 22:30:18 -05:00
|
|
|
|
// ── Skin → Theme ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-07 23:59:11 -04:00
|
|
|
|
export function fromSkin(
|
|
|
|
|
|
colors: Record<string, string>,
|
|
|
|
|
|
branding: Record<string, string>,
|
|
|
|
|
|
bannerLogo = '',
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
bannerHero = '',
|
|
|
|
|
|
toolPrefix = '',
|
|
|
|
|
|
helpHeader = ''
|
2026-04-07 23:59:11 -04:00
|
|
|
|
): Theme {
|
2026-04-02 19:06:42 -05:00
|
|
|
|
const d = DEFAULT_THEME
|
|
|
|
|
|
const c = (k: string) => colors[k]
|
|
|
|
|
|
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
const amber = c('ui_accent') ?? c('banner_accent') ?? d.color.amber
|
2026-04-14 22:30:18 -05:00
|
|
|
|
const accent = c('banner_accent') ?? c('banner_title') ?? d.color.amber
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
const dim = c('banner_dim') ?? d.color.dim
|
2026-04-14 22:30:18 -05:00
|
|
|
|
|
2026-04-02 19:06:42 -05:00
|
|
|
|
return {
|
|
|
|
|
|
color: {
|
2026-04-02 19:34:30 -05:00
|
|
|
|
gold: c('banner_title') ?? d.color.gold,
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
amber,
|
2026-04-02 19:34:30 -05:00
|
|
|
|
bronze: c('banner_border') ?? d.color.bronze,
|
|
|
|
|
|
cornsilk: c('banner_text') ?? d.color.cornsilk,
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
dim,
|
2026-04-14 22:30:18 -05:00
|
|
|
|
completionBg: c('completion_menu_bg') ?? '#FFFFFF',
|
|
|
|
|
|
completionCurrentBg: c('completion_menu_current_bg') ?? mix('#FFFFFF', accent, 0.25),
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
2026-04-02 19:34:30 -05:00
|
|
|
|
label: c('ui_label') ?? d.color.label,
|
|
|
|
|
|
ok: c('ui_ok') ?? d.color.ok,
|
|
|
|
|
|
error: c('ui_error') ?? d.color.error,
|
|
|
|
|
|
warn: c('ui_warn') ?? d.color.warn,
|
2026-04-02 19:06:42 -05:00
|
|
|
|
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
prompt: c('prompt') ?? c('banner_text') ?? d.color.prompt,
|
|
|
|
|
|
sessionLabel: c('session_label') ?? dim,
|
|
|
|
|
|
sessionBorder: c('session_border') ?? dim,
|
|
|
|
|
|
|
2026-04-02 19:34:30 -05:00
|
|
|
|
statusBg: d.color.statusBg,
|
|
|
|
|
|
statusFg: d.color.statusFg,
|
|
|
|
|
|
statusGood: c('ui_ok') ?? d.color.statusGood,
|
|
|
|
|
|
statusWarn: c('ui_warn') ?? d.color.statusWarn,
|
|
|
|
|
|
statusBad: d.color.statusBad,
|
2026-04-08 12:11:55 -05:00
|
|
|
|
statusCritical: d.color.statusCritical,
|
2026-04-16 15:50:28 -05:00
|
|
|
|
selectionBg: c('selection_bg') ?? d.color.selectionBg,
|
|
|
|
|
|
|
2026-04-08 12:11:55 -05:00
|
|
|
|
diffAdded: d.color.diffAdded,
|
|
|
|
|
|
diffRemoved: d.color.diffRemoved,
|
|
|
|
|
|
diffAddedWord: d.color.diffAddedWord,
|
2026-04-15 16:34:58 -05:00
|
|
|
|
diffRemovedWord: d.color.diffRemovedWord,
|
|
|
|
|
|
shellDollar: c('shell_dollar') ?? d.color.shellDollar
|
2026-04-02 19:06:42 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
brand: {
|
2026-04-02 19:34:30 -05:00
|
|
|
|
name: branding.agent_name ?? d.brand.name,
|
|
|
|
|
|
icon: d.brand.icon,
|
|
|
|
|
|
prompt: branding.prompt_symbol ?? d.brand.prompt,
|
|
|
|
|
|
welcome: branding.welcome ?? d.brand.welcome,
|
|
|
|
|
|
goodbye: branding.goodbye ?? d.brand.goodbye,
|
feat(tui): put the kawaii face+verb ticker in the status bar, not the thinking panel
The status bar was showing stale lifecycle text ("running…") while the
face+verb stream flickered through the thinking panel as Python pushed
thinking.delta events. That's backwards — the face ticker is the
primary "I'm alive" signal, it belongs in the status bar; the thinking
panel is for substantive reasoning and tool activity.
Status bar now reads `ui.busy`: when true, renders a local `<FaceTicker>`
cycling FACES × VERBS on a 2.5s interval, unaffected by server events.
When false, the bar shows the actual status string (ready, starting
agent…, interrupted, etc.).
Side effect: `scheduleThinkingStatus` still patches `ui.status` with
Python's face text, but while busy the bar ignores that string and uses
the ticker instead. No server-side changes needed — Python keeps
emitting thinking.delta as a liveness heartbeat, the TUI just doesn't
let it fight the status bar.
2026-04-16 20:14:25 -05:00
|
|
|
|
tool: toolPrefix || d.brand.tool,
|
|
|
|
|
|
helpHeader: branding.help_header ?? (helpHeader || d.brand.helpHeader)
|
2026-04-07 23:59:11 -04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
bannerLogo,
|
|
|
|
|
|
bannerHero
|
2026-04-02 19:06:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|