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.
This commit is contained in:
parent
23212d6b40
commit
aedc767c66
@ -6,6 +6,11 @@
|
|||||||
# All fields are optional — missing values inherit from the default skin.
|
# All fields are optional — missing values inherit from the default skin.
|
||||||
# Activate with: /skin <name> or display.skin: <name> in config.yaml
|
# Activate with: /skin <name> or display.skin: <name> in config.yaml
|
||||||
#
|
#
|
||||||
|
# Keys are marked:
|
||||||
|
# (both) — applies to both the classic CLI and the TUI
|
||||||
|
# (classic) — classic CLI only (see hermes --tui in user-guide/tui.md)
|
||||||
|
# (tui) — TUI only
|
||||||
|
#
|
||||||
# See hermes_cli/skin_engine.py for the full schema reference.
|
# See hermes_cli/skin_engine.py for the full schema reference.
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
@ -14,43 +19,47 @@ name: example
|
|||||||
description: An example custom skin — copy and modify this template
|
description: An example custom skin — copy and modify this template
|
||||||
|
|
||||||
# ── Colors ──────────────────────────────────────────────────────────────────
|
# ── Colors ──────────────────────────────────────────────────────────────────
|
||||||
# Hex color values for Rich markup. These control the CLI's visual palette.
|
# Hex color values. These control the visual palette.
|
||||||
colors:
|
colors:
|
||||||
# Banner panel (the startup welcome box)
|
# Banner panel (the startup welcome box) — (both)
|
||||||
banner_border: "#CD7F32" # Panel border
|
banner_border: "#CD7F32" # Panel border
|
||||||
banner_title: "#FFD700" # Panel title text
|
banner_title: "#FFD700" # Panel title text
|
||||||
banner_accent: "#FFBF00" # Section headers (Available Tools, Skills, etc.)
|
banner_accent: "#FFBF00" # Section headers (Available Tools, Skills, etc.)
|
||||||
banner_dim: "#B8860B" # Dim/muted text (separators, model info)
|
banner_dim: "#B8860B" # Dim/muted text (separators, model info)
|
||||||
banner_text: "#FFF8DC" # Body text (tool names, skill names)
|
banner_text: "#FFF8DC" # Body text (tool names, skill names)
|
||||||
|
|
||||||
# UI elements
|
# UI elements — (both)
|
||||||
ui_accent: "#FFBF00" # General accent color
|
ui_accent: "#FFBF00" # General accent (falls back to banner_accent)
|
||||||
ui_label: "#4dd0e1" # Labels
|
ui_label: "#4dd0e1" # Labels
|
||||||
ui_ok: "#4caf50" # Success indicators
|
ui_ok: "#4caf50" # Success indicators
|
||||||
ui_error: "#ef5350" # Error indicators
|
ui_error: "#ef5350" # Error indicators
|
||||||
ui_warn: "#ffa726" # Warning indicators
|
ui_warn: "#ffa726" # Warning indicators
|
||||||
|
|
||||||
# Input area
|
# Input area
|
||||||
prompt: "#FFF8DC" # Prompt text color
|
prompt: "#FFF8DC" # Prompt text / `❯` glyph color (both)
|
||||||
input_rule: "#CD7F32" # Horizontal rule around input
|
input_rule: "#CD7F32" # Horizontal rule above input (classic)
|
||||||
|
|
||||||
# Response box
|
# Response box — (classic)
|
||||||
response_border: "#FFD700" # Response box border (ANSI color)
|
response_border: "#FFD700" # Response box border
|
||||||
|
|
||||||
# Session display
|
# Session display — (both)
|
||||||
session_label: "#DAA520" # Session label
|
session_label: "#DAA520" # "Session: " label
|
||||||
session_border: "#8B8682" # Session ID dim color
|
session_border: "#8B8682" # Session ID text
|
||||||
|
|
||||||
# TUI surfaces
|
# TUI / CLI surfaces — (classic: status bar, voice badge, completion meta)
|
||||||
status_bar_bg: "#1a1a2e" # Status / usage bar background
|
status_bar_bg: "#1a1a2e" # Status / usage bar background (classic)
|
||||||
voice_status_bg: "#1a1a2e" # Voice-mode badge background
|
voice_status_bg: "#1a1a2e" # Voice-mode badge background (classic)
|
||||||
completion_menu_bg: "#1a1a2e" # Completion list background
|
completion_menu_bg: "#1a1a2e" # Completion list background (both)
|
||||||
completion_menu_current_bg: "#333355" # Active completion row background
|
completion_menu_current_bg: "#333355" # Active completion row background (both)
|
||||||
completion_menu_meta_bg: "#1a1a2e" # Completion meta column background
|
completion_menu_meta_bg: "#1a1a2e" # Completion meta column bg (classic)
|
||||||
completion_menu_meta_current_bg: "#333355" # Active completion meta background
|
completion_menu_meta_current_bg: "#333355" # Active meta bg (classic)
|
||||||
|
|
||||||
|
# Drag-to-select background — (tui)
|
||||||
|
selection_bg: "#3a3a55" # Uniform selection highlight in the TUI
|
||||||
|
|
||||||
# ── Spinner ─────────────────────────────────────────────────────────────────
|
# ── Spinner ─────────────────────────────────────────────────────────────────
|
||||||
# Customize the animated spinner shown during API calls and tool execution.
|
# (classic) — the TUI uses its own animated indicators; spinner config here
|
||||||
|
# is only read by the classic prompt_toolkit CLI.
|
||||||
spinner:
|
spinner:
|
||||||
# Faces shown while waiting for the API response
|
# Faces shown while waiting for the API response
|
||||||
waiting_faces:
|
waiting_faces:
|
||||||
@ -78,17 +87,17 @@ spinner:
|
|||||||
# - ["⟪▲", "▲⟫"]
|
# - ["⟪▲", "▲⟫"]
|
||||||
|
|
||||||
# ── Branding ────────────────────────────────────────────────────────────────
|
# ── Branding ────────────────────────────────────────────────────────────────
|
||||||
# Text strings used throughout the CLI interface.
|
# Text strings used throughout the interface.
|
||||||
branding:
|
branding:
|
||||||
agent_name: "Hermes Agent" # Banner title, about display
|
agent_name: "Hermes Agent" # (both) Banner title, about display
|
||||||
welcome: "Welcome! Type your message or /help for commands."
|
welcome: "Welcome! Type your message or /help for commands." # (both)
|
||||||
goodbye: "Goodbye! ⚕" # Exit message
|
goodbye: "Goodbye! ⚕" # (both) Exit message
|
||||||
response_label: " ⚕ Hermes " # Response box header label
|
response_label: " ⚕ Hermes " # (classic) Response box header label
|
||||||
prompt_symbol: "❯ " # Input prompt symbol
|
prompt_symbol: "❯ " # (both) Input prompt glyph
|
||||||
help_header: "(^_^)? Available Commands" # /help header text
|
help_header: "(^_^)? Available Commands" # (both) /help overlay title
|
||||||
|
|
||||||
# ── Tool Output ─────────────────────────────────────────────────────────────
|
# ── Tool Output ─────────────────────────────────────────────────────────────
|
||||||
# Character used as the prefix for tool output lines.
|
# Character used as the prefix for tool output lines. (both)
|
||||||
# Default is "┊" (thin dotted vertical line). Some alternatives:
|
# Default is "┊" (thin dotted vertical line). Some alternatives:
|
||||||
# "╎" (light triple dash vertical)
|
# "╎" (light triple dash vertical)
|
||||||
# "▏" (left one-eighth block)
|
# "▏" (left one-eighth block)
|
||||||
|
|||||||
@ -316,6 +316,8 @@ def resolve_skin() -> dict:
|
|||||||
"branding": skin.branding,
|
"branding": skin.branding,
|
||||||
"banner_logo": skin.banner_logo,
|
"banner_logo": skin.banner_logo,
|
||||||
"banner_hero": skin.banner_hero,
|
"banner_hero": skin.banner_hero,
|
||||||
|
"tool_prefix": skin.tool_prefix,
|
||||||
|
"help_header": (skin.branding or {}).get("help_header", ""),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@ -102,7 +102,7 @@ describe('createSlashHandler', () => {
|
|||||||
const h = createSlashHandler(ctx)
|
const h = createSlashHandler(ctx)
|
||||||
expect(h('/zzz')).toBe(true)
|
expect(h('/zzz')).toBe(true)
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(ctx.transcript.panel).toHaveBeenCalledWith('Commands', expect.any(Array))
|
expect(ctx.transcript.panel).toHaveBeenCalledWith(expect.any(String), expect.any(Array))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ describe('createSlashHandler', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(createSlashHandler(ctx)('/h')).toBe(true)
|
expect(createSlashHandler(ctx)('/h')).toBe(true)
|
||||||
expect(ctx.transcript.panel).toHaveBeenCalledWith('Commands', expect.any(Array))
|
expect(ctx.transcript.panel).toHaveBeenCalledWith(expect.any(String), expect.any(Array))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,16 @@ const ERRLIKE_RE = /\b(error|traceback|exception|failed|spawn)\b/i
|
|||||||
const statusFromBusy = () => (getUiState().busy ? 'running…' : 'ready')
|
const statusFromBusy = () => (getUiState().busy ? 'running…' : 'ready')
|
||||||
|
|
||||||
const applySkin = (s: GatewaySkin) =>
|
const applySkin = (s: GatewaySkin) =>
|
||||||
patchUiState({ theme: fromSkin(s.colors ?? {}, s.branding ?? {}, s.banner_logo ?? '', s.banner_hero ?? '') })
|
patchUiState({
|
||||||
|
theme: fromSkin(
|
||||||
|
s.colors ?? {},
|
||||||
|
s.branding ?? {},
|
||||||
|
s.banner_logo ?? '',
|
||||||
|
s.banner_hero ?? '',
|
||||||
|
s.tool_prefix ?? '',
|
||||||
|
s.help_header ?? ''
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const dropBgTask = (taskId: string) =>
|
const dropBgTask = (taskId: string) =>
|
||||||
patchUiState(state => {
|
patchUiState(state => {
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export const coreCommands: SlashCommand[] = [
|
|||||||
})
|
})
|
||||||
sections.push({ rows: HOTKEYS, title: 'Hotkeys' })
|
sections.push({ rows: HOTKEYS, title: 'Hotkeys' })
|
||||||
|
|
||||||
ctx.transcript.panel('Commands', sections)
|
ctx.transcript.panel(ctx.ui.theme.brand.helpHeader, sections)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,32 @@
|
|||||||
import { Box, type ScrollBoxHandle, Text } from '@hermes/ink'
|
import { Box, type ScrollBoxHandle, Text } from '@hermes/ink'
|
||||||
import { type ReactNode, type RefObject, useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
import { type ReactNode, type RefObject, useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
||||||
|
|
||||||
|
import { FACES } from '../content/faces.js'
|
||||||
|
import { VERBS } from '../content/verbs.js'
|
||||||
import { fmtDuration } from '../domain/messages.js'
|
import { fmtDuration } from '../domain/messages.js'
|
||||||
import { stickyPromptFromViewport } from '../domain/viewport.js'
|
import { stickyPromptFromViewport } from '../domain/viewport.js'
|
||||||
import { fmtK } from '../lib/text.js'
|
import { fmtK } from '../lib/text.js'
|
||||||
import type { Theme } from '../theme.js'
|
import type { Theme } from '../theme.js'
|
||||||
import type { Msg, Usage } from '../types.js'
|
import type { Msg, Usage } from '../types.js'
|
||||||
|
|
||||||
|
const FACE_TICK_MS = 2500
|
||||||
|
|
||||||
|
function FaceTicker({ color }: { color: string }) {
|
||||||
|
const [tick, setTick] = useState(() => Math.floor(Math.random() * 1000))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const id = setInterval(() => setTick(n => n + 1), FACE_TICK_MS)
|
||||||
|
|
||||||
|
return () => clearInterval(id)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text color={color}>
|
||||||
|
{FACES[tick % FACES.length]} {VERBS[tick % VERBS.length]}…
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ctxBarColor(pct: number | undefined, t: Theme) {
|
function ctxBarColor(pct: number | undefined, t: Theme) {
|
||||||
if (pct == null) {
|
if (pct == null) {
|
||||||
return t.color.dim
|
return t.color.dim
|
||||||
@ -73,6 +93,7 @@ export function GoodVibesHeart({ tick, t }: { tick: number; t: Theme }) {
|
|||||||
export function StatusRule({
|
export function StatusRule({
|
||||||
cwdLabel,
|
cwdLabel,
|
||||||
cols,
|
cols,
|
||||||
|
busy,
|
||||||
status,
|
status,
|
||||||
statusColor,
|
statusColor,
|
||||||
model,
|
model,
|
||||||
@ -84,6 +105,7 @@ export function StatusRule({
|
|||||||
}: {
|
}: {
|
||||||
cwdLabel: string
|
cwdLabel: string
|
||||||
cols: number
|
cols: number
|
||||||
|
busy: boolean
|
||||||
status: string
|
status: string
|
||||||
statusColor: string
|
statusColor: string
|
||||||
model: string
|
model: string
|
||||||
@ -111,7 +133,7 @@ export function StatusRule({
|
|||||||
<Box flexShrink={1} width={leftWidth}>
|
<Box flexShrink={1} width={leftWidth}>
|
||||||
<Text color={t.color.bronze} wrap="truncate-end">
|
<Text color={t.color.bronze} wrap="truncate-end">
|
||||||
{'─ '}
|
{'─ '}
|
||||||
<Text color={statusColor}>{status}</Text>
|
{busy ? <FaceTicker color={statusColor} /> : <Text color={statusColor}>{status}</Text>}
|
||||||
<Text color={t.color.dim}> │ {model}</Text>
|
<Text color={t.color.dim}> │ {model}</Text>
|
||||||
{ctxLabel ? <Text color={t.color.dim}> │ {ctxLabel}</Text> : null}
|
{ctxLabel ? <Text color={t.color.dim}> │ {ctxLabel}</Text> : null}
|
||||||
{bar ? (
|
{bar ? (
|
||||||
|
|||||||
@ -138,6 +138,7 @@ const ComposerPane = memo(function ComposerPane({
|
|||||||
{ui.statusBar && (
|
{ui.statusBar && (
|
||||||
<StatusRule
|
<StatusRule
|
||||||
bgCount={ui.bgTasks.size}
|
bgCount={ui.bgTasks.size}
|
||||||
|
busy={ui.busy}
|
||||||
cols={composer.cols}
|
cols={composer.cols}
|
||||||
cwdLabel={status.cwdLabel}
|
cwdLabel={status.cwdLabel}
|
||||||
model={ui.info?.model?.split('/').pop() ?? ''}
|
model={ui.info?.model?.split('/').pop() ?? ''}
|
||||||
@ -181,7 +182,7 @@ const ComposerPane = memo(function ComposerPane({
|
|||||||
{sh ? (
|
{sh ? (
|
||||||
<Text color={ui.theme.color.shellDollar}>$ </Text>
|
<Text color={ui.theme.color.shellDollar}>$ </Text>
|
||||||
) : (
|
) : (
|
||||||
<Text bold color={ui.theme.color.gold}>
|
<Text bold color={ui.theme.color.prompt}>
|
||||||
{composer.inputBuf.length ? ' ' : `${ui.theme.brand.prompt} `}
|
{composer.inputBuf.length ? ' ' : `${ui.theme.brand.prompt} `}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -106,7 +106,12 @@ export function SessionPanel({ info, sid, t }: { info: SessionInfo; sid?: string
|
|||||||
<Text color={t.color.dim} wrap="truncate-end">
|
<Text color={t.color.dim} wrap="truncate-end">
|
||||||
{cwd}
|
{cwd}
|
||||||
</Text>
|
</Text>
|
||||||
{sid && <Text color={t.color.dim}>Session: {sid}</Text>}
|
{sid && (
|
||||||
|
<Text>
|
||||||
|
<Text color={t.color.sessionLabel}>Session: </Text>
|
||||||
|
<Text color={t.color.sessionBorder}>{sid}</Text>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box flexDirection="column" width={w}>
|
<Box flexDirection="column" width={w}>
|
||||||
|
|||||||
@ -5,6 +5,8 @@ export interface GatewaySkin {
|
|||||||
banner_logo?: string
|
banner_logo?: string
|
||||||
branding?: Record<string, string>
|
branding?: Record<string, string>
|
||||||
colors?: Record<string, string>
|
colors?: Record<string, string>
|
||||||
|
help_header?: string
|
||||||
|
tool_prefix?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GatewayCompletionItem {
|
export interface GatewayCompletionItem {
|
||||||
|
|||||||
@ -12,6 +12,10 @@ export interface ThemeColors {
|
|||||||
error: string
|
error: string
|
||||||
warn: string
|
warn: string
|
||||||
|
|
||||||
|
prompt: string
|
||||||
|
sessionLabel: string
|
||||||
|
sessionBorder: string
|
||||||
|
|
||||||
statusBg: string
|
statusBg: string
|
||||||
statusFg: string
|
statusFg: string
|
||||||
statusGood: string
|
statusGood: string
|
||||||
@ -35,6 +39,7 @@ export interface ThemeBrand {
|
|||||||
welcome: string
|
welcome: string
|
||||||
goodbye: string
|
goodbye: string
|
||||||
tool: string
|
tool: string
|
||||||
|
helpHeader: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
@ -88,6 +93,10 @@ export const DEFAULT_THEME: Theme = {
|
|||||||
error: '#ef5350',
|
error: '#ef5350',
|
||||||
warn: '#ffa726',
|
warn: '#ffa726',
|
||||||
|
|
||||||
|
prompt: '#FFF8DC',
|
||||||
|
sessionLabel: '#B8860B',
|
||||||
|
sessionBorder: '#B8860B',
|
||||||
|
|
||||||
statusBg: '#1a1a2e',
|
statusBg: '#1a1a2e',
|
||||||
statusFg: '#C0C0C0',
|
statusFg: '#C0C0C0',
|
||||||
statusGood: '#8FBC8F',
|
statusGood: '#8FBC8F',
|
||||||
@ -109,7 +118,8 @@ export const DEFAULT_THEME: Theme = {
|
|||||||
prompt: '❯',
|
prompt: '❯',
|
||||||
welcome: 'Type your message or /help for commands.',
|
welcome: 'Type your message or /help for commands.',
|
||||||
goodbye: 'Goodbye! ⚕',
|
goodbye: 'Goodbye! ⚕',
|
||||||
tool: '┊'
|
tool: '┊',
|
||||||
|
helpHeader: '(^_^)? Commands'
|
||||||
},
|
},
|
||||||
|
|
||||||
bannerLogo: '',
|
bannerLogo: '',
|
||||||
@ -122,20 +132,24 @@ export function fromSkin(
|
|||||||
colors: Record<string, string>,
|
colors: Record<string, string>,
|
||||||
branding: Record<string, string>,
|
branding: Record<string, string>,
|
||||||
bannerLogo = '',
|
bannerLogo = '',
|
||||||
bannerHero = ''
|
bannerHero = '',
|
||||||
|
toolPrefix = '',
|
||||||
|
helpHeader = ''
|
||||||
): Theme {
|
): Theme {
|
||||||
const d = DEFAULT_THEME
|
const d = DEFAULT_THEME
|
||||||
const c = (k: string) => colors[k]
|
const c = (k: string) => colors[k]
|
||||||
|
|
||||||
|
const amber = c('ui_accent') ?? c('banner_accent') ?? d.color.amber
|
||||||
const accent = c('banner_accent') ?? c('banner_title') ?? d.color.amber
|
const accent = c('banner_accent') ?? c('banner_title') ?? d.color.amber
|
||||||
|
const dim = c('banner_dim') ?? d.color.dim
|
||||||
|
|
||||||
return {
|
return {
|
||||||
color: {
|
color: {
|
||||||
gold: c('banner_title') ?? d.color.gold,
|
gold: c('banner_title') ?? d.color.gold,
|
||||||
amber: c('banner_accent') ?? d.color.amber,
|
amber,
|
||||||
bronze: c('banner_border') ?? d.color.bronze,
|
bronze: c('banner_border') ?? d.color.bronze,
|
||||||
cornsilk: c('banner_text') ?? d.color.cornsilk,
|
cornsilk: c('banner_text') ?? d.color.cornsilk,
|
||||||
dim: c('banner_dim') ?? d.color.dim,
|
dim,
|
||||||
completionBg: c('completion_menu_bg') ?? '#FFFFFF',
|
completionBg: c('completion_menu_bg') ?? '#FFFFFF',
|
||||||
completionCurrentBg: c('completion_menu_current_bg') ?? mix('#FFFFFF', accent, 0.25),
|
completionCurrentBg: c('completion_menu_current_bg') ?? mix('#FFFFFF', accent, 0.25),
|
||||||
|
|
||||||
@ -144,6 +158,10 @@ export function fromSkin(
|
|||||||
error: c('ui_error') ?? d.color.error,
|
error: c('ui_error') ?? d.color.error,
|
||||||
warn: c('ui_warn') ?? d.color.warn,
|
warn: c('ui_warn') ?? d.color.warn,
|
||||||
|
|
||||||
|
prompt: c('prompt') ?? c('banner_text') ?? d.color.prompt,
|
||||||
|
sessionLabel: c('session_label') ?? dim,
|
||||||
|
sessionBorder: c('session_border') ?? dim,
|
||||||
|
|
||||||
statusBg: d.color.statusBg,
|
statusBg: d.color.statusBg,
|
||||||
statusFg: d.color.statusFg,
|
statusFg: d.color.statusFg,
|
||||||
statusGood: c('ui_ok') ?? d.color.statusGood,
|
statusGood: c('ui_ok') ?? d.color.statusGood,
|
||||||
@ -165,7 +183,8 @@ export function fromSkin(
|
|||||||
prompt: branding.prompt_symbol ?? d.brand.prompt,
|
prompt: branding.prompt_symbol ?? d.brand.prompt,
|
||||||
welcome: branding.welcome ?? d.brand.welcome,
|
welcome: branding.welcome ?? d.brand.welcome,
|
||||||
goodbye: branding.goodbye ?? d.brand.goodbye,
|
goodbye: branding.goodbye ?? d.brand.goodbye,
|
||||||
tool: d.brand.tool
|
tool: toolPrefix || d.brand.tool,
|
||||||
|
helpHeader: branding.help_header ?? (helpHeader || d.brand.helpHeader)
|
||||||
},
|
},
|
||||||
|
|
||||||
bannerLogo,
|
bannerLogo,
|
||||||
|
|||||||
@ -48,7 +48,7 @@ The classic CLI remains available as the default. Anything documented in [CLI In
|
|||||||
- **Alternate-screen rendering** — differential updates mean no flicker when streaming, no scrollback clutter after you quit.
|
- **Alternate-screen rendering** — differential updates mean no flicker when streaming, no scrollback clutter after you quit.
|
||||||
- **Composer affordances** — inline paste-collapse for long snippets, image paste from the clipboard (`Alt+V`), bracketed-paste safety.
|
- **Composer affordances** — inline paste-collapse for long snippets, image paste from the clipboard (`Alt+V`), bracketed-paste safety.
|
||||||
|
|
||||||
Same [skins](features/skins.md) and [personalities](features/personality.md) apply. Switch mid-session with `/skin ares`, `/personality pirate`, `/theme daylight`, and the UI repaints live.
|
Same [skins](features/skins.md) and [personalities](features/personality.md) apply. Switch mid-session with `/skin ares`, `/personality pirate`, and the UI repaints live. Skin keys are marked `(both)`, `(classic)`, or `(tui)` in [`example-skin.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/docs/skins/example-skin.yaml) so you can see at a glance what applies where — the TUI honors the banner palette, UI colors, prompt glyph/color, session display, completion menu, selection bg, `tool_prefix`, and `help_header`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user