diff --git a/AGENTS.md b/AGENTS.md index 7aef595a..f3ed963f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,8 @@ hermes-agent/ │ ├── config.py # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration │ ├── commands.py # Slash command definitions + SlashCommandCompleter │ ├── callbacks.py # Terminal callbacks (clarify, sudo, approval) -│ └── setup.py # Interactive setup wizard +│ ├── setup.py # Interactive setup wizard +│ └── skin_engine.py # Skin/theme engine — CLI visual customization ├── tools/ # Tool implementations (one file per tool) │ ├── registry.py # Central tool registry (schemas, handlers, dispatch) │ ├── approval.py # Dangerous command detection @@ -121,6 +122,7 @@ Messages follow OpenAI format: `{"role": "system/user/assistant/tool", ...}`. Re - **Rich** for banner/panels, **prompt_toolkit** for input with autocomplete - **KawaiiSpinner** (`agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results - `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML +- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text - `process_command()` is a method on `HermesCLI` (not in commands.py) - Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching @@ -195,6 +197,94 @@ The registry handles schema collection, dispatch, availability checking, and err --- +## Skin/Theme System + +The skin engine (`hermes_cli/skin_engine.py`) provides data-driven CLI visual customization. Skins are **pure data** — no code changes needed to add a new skin. + +### Architecture + +``` +hermes_cli/skin_engine.py # SkinConfig dataclass, built-in skins, YAML loader +~/.hermes/skins/*.yaml # User-installed custom skins (drop-in) +``` + +- `init_skin_from_config()` — called at CLI startup, reads `display.skin` from config +- `get_active_skin()` — returns cached `SkinConfig` for the current skin +- `set_active_skin(name)` — switches skin at runtime (used by `/skin` command) +- `load_skin(name)` — loads from user skins first, then built-ins, then falls back to default +- Missing skin values inherit from the `default` skin automatically + +### What skins customize + +| Element | Skin Key | Used By | +|---------|----------|---------| +| Banner panel border | `colors.banner_border` | `banner.py` | +| Banner panel title | `colors.banner_title` | `banner.py` | +| Banner section headers | `colors.banner_accent` | `banner.py` | +| Banner dim text | `colors.banner_dim` | `banner.py` | +| Banner body text | `colors.banner_text` | `banner.py` | +| Response box border | `colors.response_border` | `cli.py` | +| Spinner faces (waiting) | `spinner.waiting_faces` | `display.py` | +| Spinner faces (thinking) | `spinner.thinking_faces` | `display.py` | +| Spinner verbs | `spinner.thinking_verbs` | `display.py` | +| Spinner wings (optional) | `spinner.wings` | `display.py` | +| Tool output prefix | `tool_prefix` | `display.py` | +| Agent name | `branding.agent_name` | `banner.py`, `cli.py` | +| Welcome message | `branding.welcome` | `cli.py` | +| Response box label | `branding.response_label` | `cli.py` | +| Prompt symbol | `branding.prompt_symbol` | `cli.py` | + +### Built-in skins + +- `default` — Classic Hermes gold/kawaii (the current look) +- `ares` — Crimson/bronze war-god theme with custom spinner wings +- `mono` — Clean grayscale monochrome +- `slate` — Cool blue developer-focused theme + +### Adding a built-in skin + +Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`: + +```python +"mytheme": { + "name": "mytheme", + "description": "Short description", + "colors": { ... }, + "spinner": { ... }, + "branding": { ... }, + "tool_prefix": "┊", +}, +``` + +### User skins (YAML) + +Users create `~/.hermes/skins/.yaml`: + +```yaml +name: cyberpunk +description: Neon-soaked terminal theme + +colors: + banner_border: "#FF00FF" + banner_title: "#00FFFF" + banner_accent: "#FF1493" + +spinner: + thinking_verbs: ["jacking in", "decrypting", "uploading"] + wings: + - ["⟨⚡", "⚡⟩"] + +branding: + agent_name: "Cyber Agent" + response_label: " ⚡ Cyber " + +tool_prefix: "▏" +``` + +Activate with `/skin cyberpunk` or `display.skin: cyberpunk` in config.yaml. + +--- + ## Important Policies ### Prompt Caching Must Not Break diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ed6c833..e66dbb3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,7 +139,8 @@ hermes-agent/ │ ├── commands.py # Slash command definitions + autocomplete │ ├── callbacks.py # Interactive callbacks (clarify, sudo, approval) │ ├── doctor.py # Diagnostics -│ └── skills_hub.py # Skills Hub CLI + /skills slash command +│ ├── skills_hub.py # Skills Hub CLI + /skills slash command +│ └── skin_engine.py # Skin/theme engine — data-driven CLI visual customization │ ├── tools/ # Tool implementations (self-registering) │ ├── registry.py # Central tool registry (schemas, handlers, dispatch) @@ -375,6 +376,56 @@ If the field is omitted or empty, the skill loads on all platforms (backward com --- +## Adding a Skin / Theme + +Hermes uses a data-driven skin system — no code changes needed to add a new skin. + +**Option A: User skin (YAML file)** + +Create `~/.hermes/skins/.yaml`: + +```yaml +name: mytheme +description: Short description of the theme + +colors: + banner_border: "#HEX" # Panel border color + banner_title: "#HEX" # Panel title color + banner_accent: "#HEX" # Section header color + banner_dim: "#HEX" # Muted/dim text color + banner_text: "#HEX" # Body text color + response_border: "#HEX" # Response box border + +spinner: + waiting_faces: ["(⚔)", "(⛨)"] + thinking_faces: ["(⚔)", "(⌁)"] + thinking_verbs: ["forging", "plotting"] + wings: # Optional left/right decorations + - ["⟪⚔", "⚔⟫"] + +branding: + agent_name: "My Agent" + welcome: "Welcome message" + response_label: " ⚔ Agent " + prompt_symbol: "⚔ ❯ " + +tool_prefix: "╎" # Tool output line prefix +``` + +All fields are optional — missing values inherit from the default skin. + +**Option B: Built-in skin** + +Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`. Use the same schema as above but as a Python dict. Built-in skins ship with the package and are always available. + +**Activating:** +- CLI: `/skin mytheme` or set `display.skin: mytheme` in config.yaml +- Config: `display: { skin: mytheme }` + +See `hermes_cli/skin_engine.py` for the full schema and existing skins as examples. + +--- + ## Cross-Platform Compatibility Hermes runs on Linux, macOS, and Windows. When writing code that touches the OS: diff --git a/cli-config.yaml.example b/cli-config.yaml.example index 681fa1ff..080f49cd 100644 --- a/cli-config.yaml.example +++ b/cli-config.yaml.example @@ -659,3 +659,44 @@ display: # Useful for long-running tasks — your terminal will ding when the agent is done. # Works over SSH. Most terminals can be configured to flash the taskbar or play a sound. bell_on_complete: false + + # ─────────────────────────────────────────────────────────────────────────── + # Skin / Theme + # ─────────────────────────────────────────────────────────────────────────── + # Customize CLI visual appearance — banner colors, spinner faces, tool prefix, + # response box label, and branding text. Change at runtime with /skin . + # + # Built-in skins: + # default — Classic Hermes gold/kawaii + # ares — Crimson/bronze war-god theme with spinner wings + # mono — Clean grayscale monochrome + # slate — Cool blue developer-focused + # + # Custom skins: drop a YAML file in ~/.hermes/skins/.yaml + # Schema (all fields optional, missing values inherit from default): + # + # name: my-theme + # description: Short description + # colors: + # banner_border: "#HEX" # Panel border + # banner_title: "#HEX" # Panel title + # banner_accent: "#HEX" # Section headers (Available Tools, etc.) + # banner_dim: "#HEX" # Dim/muted text + # banner_text: "#HEX" # Body text (tool names, skill names) + # ui_accent: "#HEX" # UI accent color + # response_border: "#HEX" # Response box border color + # spinner: + # waiting_faces: ["(⚔)", "(⛨)"] # Faces shown while waiting + # thinking_faces: ["(⚔)", "(⌁)"] # Faces shown while thinking + # thinking_verbs: ["forging", "plotting"] # Verbs for spinner messages + # wings: # Optional left/right spinner decorations + # - ["⟪⚔", "⚔⟫"] + # - ["⟪▲", "▲⟫"] + # branding: + # agent_name: "My Agent" # Banner title and branding + # welcome: "Welcome message" # Shown at CLI startup + # response_label: " ⚔ Agent " # Response box header label + # prompt_symbol: "⚔ ❯ " # Prompt symbol + # tool_prefix: "╎" # Tool output line prefix (default: ┊) + # + skin: default diff --git a/docs/skins/example-skin.yaml b/docs/skins/example-skin.yaml new file mode 100644 index 00000000..612c841e --- /dev/null +++ b/docs/skins/example-skin.yaml @@ -0,0 +1,89 @@ +# ============================================================================ +# Hermes Agent — Example Skin Template +# ============================================================================ +# +# Copy this file to ~/.hermes/skins/.yaml to create a custom skin. +# All fields are optional — missing values inherit from the default skin. +# Activate with: /skin or display.skin: in config.yaml +# +# See hermes_cli/skin_engine.py for the full schema reference. +# ============================================================================ + +# Required: unique skin name (used in /skin command and config) +name: example +description: An example custom skin — copy and modify this template + +# ── Colors ────────────────────────────────────────────────────────────────── +# Hex color values for Rich markup. These control the CLI's visual palette. +colors: + # Banner panel (the startup welcome box) + banner_border: "#CD7F32" # Panel border + banner_title: "#FFD700" # Panel title text + banner_accent: "#FFBF00" # Section headers (Available Tools, Skills, etc.) + banner_dim: "#B8860B" # Dim/muted text (separators, model info) + banner_text: "#FFF8DC" # Body text (tool names, skill names) + + # UI elements + ui_accent: "#FFBF00" # General accent color + ui_label: "#4dd0e1" # Labels + ui_ok: "#4caf50" # Success indicators + ui_error: "#ef5350" # Error indicators + ui_warn: "#ffa726" # Warning indicators + + # Input area + prompt: "#FFF8DC" # Prompt text color + input_rule: "#CD7F32" # Horizontal rule around input + + # Response box + response_border: "#FFD700" # Response box border (ANSI color) + + # Session display + session_label: "#DAA520" # Session label + session_border: "#8B8682" # Session ID dim color + +# ── Spinner ───────────────────────────────────────────────────────────────── +# Customize the animated spinner shown during API calls and tool execution. +spinner: + # Faces shown while waiting for the API response + waiting_faces: + - "(。◕‿◕。)" + - "(◕‿◕✿)" + - "٩(◕‿◕。)۶" + + # Faces shown during extended thinking/reasoning + thinking_faces: + - "(。•́︿•̀。)" + - "(◔_◔)" + - "(¬‿¬)" + + # Verbs used in spinner messages (e.g., "pondering your request...") + thinking_verbs: + - "pondering" + - "contemplating" + - "musing" + - "ruminating" + + # Optional: left/right decorations around the spinner + # Each entry is a [left, right] pair. Omit entirely for no wings. + # wings: + # - ["⟪⚔", "⚔⟫"] + # - ["⟪▲", "▲⟫"] + +# ── Branding ──────────────────────────────────────────────────────────────── +# Text strings used throughout the CLI interface. +branding: + agent_name: "Hermes Agent" # Banner title, about display + welcome: "Welcome! Type your message or /help for commands." + goodbye: "Goodbye! ⚕" # Exit message + response_label: " ⚕ Hermes " # Response box header label + prompt_symbol: "❯ " # Input prompt symbol + help_header: "(^_^)? Available Commands" # /help header text + +# ── Tool Output ───────────────────────────────────────────────────────────── +# Character used as the prefix for tool output lines. +# Default is "┊" (thin dotted vertical line). Some alternatives: +# "╎" (light triple dash vertical) +# "▏" (left one-eighth block) +# "│" (box drawing light vertical) +# "┃" (box drawing heavy vertical) +tool_prefix: "┊" diff --git a/hermes_cli/skin_engine.py b/hermes_cli/skin_engine.py index ea97ac38..e6a196d0 100644 --- a/hermes_cli/skin_engine.py +++ b/hermes_cli/skin_engine.py @@ -2,19 +2,91 @@ A data-driven skin system that lets users customize the CLI's visual appearance. Skins are defined as YAML files in ~/.hermes/skins/ or as built-in presets. +No code changes are needed to add a new skin. -Each skin defines: -- colors: banner and UI color palette (hex values for Rich markup) -- spinner: kawaii faces, thinking verbs, optional wings -- branding: agent name, welcome/goodbye messages, prompt symbol -- tool_prefix: character used for tool output lines (default: ┊) +SKIN YAML SCHEMA +================ + +All fields are optional. Missing values inherit from the ``default`` skin. + +.. code-block:: yaml + + # Required: skin identity + name: mytheme # Unique skin name (lowercase, hyphens ok) + description: Short description # Shown in /skin listing + + # Colors: hex values for Rich markup (banner, UI, response box) + colors: + banner_border: "#CD7F32" # Panel border color + banner_title: "#FFD700" # Panel title text color + banner_accent: "#FFBF00" # Section headers (Available Tools, etc.) + banner_dim: "#B8860B" # Dim/muted text (separators, labels) + banner_text: "#FFF8DC" # Body text (tool names, skill names) + ui_accent: "#FFBF00" # General UI accent + ui_label: "#4dd0e1" # UI labels + ui_ok: "#4caf50" # Success indicators + ui_error: "#ef5350" # Error indicators + ui_warn: "#ffa726" # Warning indicators + prompt: "#FFF8DC" # Prompt text color + input_rule: "#CD7F32" # Input area horizontal rule + response_border: "#FFD700" # Response box border (ANSI) + session_label: "#DAA520" # Session label color + session_border: "#8B8682" # Session ID dim color + + # Spinner: customize the animated spinner during API calls + spinner: + waiting_faces: # Faces shown while waiting for API + - "(⚔)" + - "(⛨)" + thinking_faces: # Faces shown during reasoning + - "(⌁)" + - "(<>)" + thinking_verbs: # Verbs for spinner messages + - "forging" + - "plotting" + wings: # Optional left/right spinner decorations + - ["⟪⚔", "⚔⟫"] # Each entry is [left, right] pair + - ["⟪▲", "▲⟫"] + + # Branding: text strings used throughout the CLI + branding: + agent_name: "Hermes Agent" # Banner title, status display + welcome: "Welcome message" # Shown at CLI startup + goodbye: "Goodbye! ⚕" # Shown on exit + response_label: " ⚕ Hermes " # Response box header label + prompt_symbol: "❯ " # Input prompt symbol + help_header: "(^_^)? Commands" # /help header text + + # Tool prefix: character for tool output lines (default: ┊) + tool_prefix: "┊" + +USAGE +===== + +.. code-block:: python -Usage: from hermes_cli.skin_engine import get_active_skin, list_skins, set_active_skin skin = get_active_skin() - print(skin.colors["banner_title"]) # "#FFD700" - print(skin.spinner["thinking_verbs"]) # ["pondering", ...] + print(skin.colors["banner_title"]) # "#FFD700" + print(skin.get_branding("agent_name")) # "Hermes Agent" + + set_active_skin("ares") # Switch to built-in ares skin + set_active_skin("mytheme") # Switch to user skin from ~/.hermes/skins/ + +BUILT-IN SKINS +============== + +- ``default`` — Classic Hermes gold/kawaii (the current look) +- ``ares`` — Crimson/bronze war-god theme with custom spinner wings +- ``mono`` — Clean grayscale monochrome +- ``slate`` — Cool blue developer-focused theme + +USER SKINS +========== + +Drop a YAML file in ``~/.hermes/skins/.yaml`` following the schema above. +Activate with ``/skin `` in the CLI or ``display.skin: `` in config.yaml. """ import logging