feat(cli): show random tip on new session start (#8225)

Add a 'tip of the day' feature that displays a random one-liner about
Hermes Agent features on every new session — CLI startup, /clear, /new,
and gateway /new across all messaging platforms.

- New hermes_cli/tips.py module with 210 curated tips covering slash
  commands, keybindings, CLI flags, config options, tools, gateway
  platforms, profiles, sessions, memory, skills, cron, voice, security,
  and more
- CLI: tips display in skin-aware dim gold color after the welcome line
- Gateway: tips append to the /new and /reset response on all platforms
- Fully wrapped in try/except — tips are non-critical and never break
  startup or reset

Display format (CLI):
  ✦ Tip: /btw <question> asks a quick side question without tools or history.

Display format (gateway):
   Session reset! Starting fresh.
  ✦ Tip: hermes -c resumes your most recent CLI session.
This commit is contained in:
Teknium 2026-04-12 00:34:01 -07:00 committed by GitHub
parent 36f57dbc51
commit fdf55e0fe9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 401 additions and 2 deletions

35
cli.py
View file

@ -5244,9 +5244,33 @@ class HermesCLI:
context_length=ctx_len,
)
_cprint(" ✨ (◕‿◕)✨ Fresh start! Screen cleared and conversation reset.\n")
# Show a random tip on new session
try:
from hermes_cli.tips import get_random_tip
_tip = get_random_tip()
try:
from hermes_cli.skin_engine import get_active_skin
_tip_color = get_active_skin().get_color("banner_dim", "#B8860B")
except Exception:
_tip_color = "#B8860B"
cc.print(f"[dim {_tip_color}]✦ Tip: {_tip}[/]")
except Exception:
pass
else:
self.show_banner()
print(" ✨ (◕‿◕)✨ Fresh start! Screen cleared and conversation reset.\n")
# Show a random tip on new session
try:
from hermes_cli.tips import get_random_tip
_tip = get_random_tip()
try:
from hermes_cli.skin_engine import get_active_skin
_tip_color = get_active_skin().get_color("banner_dim", "#B8860B")
except Exception:
_tip_color = "#B8860B"
self.console.print(f"[dim {_tip_color}]✦ Tip: {_tip}[/]")
except Exception:
pass
elif canonical == "history":
self.show_history()
elif canonical == "title":
@ -8075,6 +8099,17 @@ class HermesCLI:
_welcome_text = "Welcome to Hermes Agent! Type your message or /help for commands."
_welcome_color = "#FFF8DC"
self.console.print(f"[{_welcome_color}]{_welcome_text}[/]")
# Show a random tip to help users discover features
try:
from hermes_cli.tips import get_random_tip
_tip = get_random_tip()
try:
_tip_color = _welcome_skin.get_color("banner_dim", "#B8860B")
except Exception:
_tip_color = "#B8860B"
self.console.print(f"[dim {_tip_color}]✦ Tip: {_tip}[/]")
except Exception:
pass # Tips are non-critical — never break startup
if self.preloaded_skills and not self._startup_skills_line_shown:
skills_label = ", ".join(self.preloaded_skills)
self.console.print(

View file

@ -3965,9 +3965,16 @@ class GatewayRunner:
except Exception:
pass
# Append a random tip to the reset message
try:
from hermes_cli.tips import get_random_tip
_tip_line = f"\n✦ Tip: {get_random_tip()}"
except Exception:
_tip_line = ""
if session_info:
return f"{header}\n\n{session_info}"
return header
return f"{header}\n\n{session_info}{_tip_line}"
return f"{header}{_tip_line}"
async def _handle_profile_command(self, event: MessageEvent) -> str:
"""Handle /profile — show active profile name and home directory."""

280
hermes_cli/tips.py Normal file
View file

@ -0,0 +1,280 @@
"""Random tips shown at CLI session start to help users discover features."""
import random
from typing import Optional
# ---------------------------------------------------------------------------
# Tip corpus — ~200 one-liners covering slash commands, CLI flags, config,
# keybindings, tools, gateway, skills, profiles, and workflow tricks.
# ---------------------------------------------------------------------------
TIPS = [
# --- Slash Commands ---
"/btw <question> asks a quick side question without tools or history — great for clarifications.",
"/background <prompt> runs a task in a separate session while your current one stays free.",
"/branch forks the current session so you can explore a different direction without losing progress.",
"/compress manually compresses conversation context when things get long.",
"/rollback lists filesystem checkpoints — restore files the agent modified to any prior state.",
"/rollback diff 2 previews what changed since checkpoint 2 without restoring anything.",
"/rollback 2 src/file.py restores a single file from a specific checkpoint.",
"/title \"my project\" names your session — resume it later with /resume or hermes -c.",
"/resume picks up where you left off in a previously named session.",
"/queue <prompt> queues a message for the next turn without interrupting the current one.",
"/undo removes the last user/assistant exchange from the conversation.",
"/retry resends your last message — useful when the agent's response wasn't quite right.",
"/verbose cycles tool progress display: off → new → all → verbose.",
"/reasoning high increases the model's thinking depth. /reasoning show displays the reasoning.",
"/fast toggles priority processing for faster API responses (provider-dependent).",
"/yolo skips all dangerous command approval prompts for the rest of the session.",
"/model lets you switch models mid-session — try /model sonnet or /model gpt-5.",
"/model --global changes your default model permanently.",
"/personality pirate sets a fun personality — 14 built-in options from kawaii to shakespeare.",
"/skin changes the CLI theme — try ares, mono, slate, poseidon, or charizard.",
"/statusbar toggles a persistent bar showing model, tokens, context fill %, cost, and duration.",
"/tools disable browser temporarily removes browser tools for the current session.",
"/browser connect attaches browser tools to your running Chrome instance via CDP.",
"/plugins lists installed plugins and their status.",
"/cron manages scheduled tasks — set up recurring prompts with delivery to any platform.",
"/reload-mcp hot-reloads MCP server configuration without restarting.",
"/usage shows token usage, cost breakdown, and session duration.",
"/insights shows usage analytics for the last 30 days.",
"/paste checks your clipboard for an image and attaches it to your next message.",
"/profile shows which profile is active and its home directory.",
"/config shows your current configuration at a glance.",
"/stop kills all running background processes spawned by the agent.",
# --- @ Context References ---
"@file:path/to/file.py injects file contents directly into your message.",
"@file:main.py:10-50 injects only lines 10-50 of a file.",
"@folder:src/ injects a directory tree listing.",
"@diff injects your unstaged git changes into the message.",
"@staged injects your staged git changes (git diff --staged).",
"@git:5 injects the last 5 commits with full patches.",
"@url:https://example.com fetches and injects a web page's content.",
"Typing @ triggers filesystem path completion — navigate to any file interactively.",
"Combine multiple references: \"Review @file:main.py and @file:test.py for consistency.\"",
# --- Keybindings ---
"Alt+Enter (or Ctrl+J) inserts a newline for multi-line input.",
"Ctrl+C interrupts the agent. Double-press within 2 seconds to force exit.",
"Ctrl+Z suspends Hermes to the background — run fg in your shell to resume.",
"Tab accepts auto-suggestion ghost text or autocompletes slash commands.",
"Type a new message while the agent is working to interrupt and redirect it.",
"Alt+V pastes an image from your clipboard into the conversation.",
"Pasting 5+ lines auto-saves to a file and inserts a compact reference instead.",
# --- CLI Flags ---
"hermes -c resumes your most recent CLI session. hermes -c \"project name\" resumes by title.",
"hermes -w creates an isolated git worktree — perfect for parallel agent workflows.",
"hermes -w -q \"Fix issue #42\" combines worktree isolation with a one-shot query.",
"hermes chat -t web,terminal enables only specific toolsets for a focused session.",
"hermes chat -s github-pr-workflow preloads a skill at launch.",
"hermes chat -q \"query\" runs a single non-interactive query and exits.",
"hermes chat --max-turns 200 overrides the default 90-iteration limit per turn.",
"hermes chat --checkpoints enables filesystem snapshots before every destructive file change.",
"hermes --yolo bypasses all dangerous command approval prompts for the entire session.",
"hermes chat --source telegram tags the session for filtering in hermes sessions list.",
"hermes -p work chat runs under a specific profile without changing your default.",
# --- CLI Subcommands ---
"hermes doctor --fix diagnoses and auto-repairs config and dependency issues.",
"hermes dump outputs a compact setup summary — great for bug reports.",
"hermes config set KEY VALUE auto-routes secrets to .env and everything else to config.yaml.",
"hermes config edit opens config.yaml in your default editor.",
"hermes config check scans for missing or stale configuration options.",
"hermes sessions browse opens an interactive session picker with search.",
"hermes sessions stats shows session counts by platform and database size.",
"hermes sessions prune --older-than 30 cleans up old sessions.",
"hermes skills search react --source skills-sh searches the skills.sh public directory.",
"hermes skills check scans installed hub skills for upstream updates.",
"hermes skills tap add myorg/skills-repo adds a custom GitHub skill source.",
"hermes skills snapshot export setup.json exports your skill configuration for backup or sharing.",
"hermes mcp add github --command npx adds MCP servers from the command line.",
"hermes mcp serve runs Hermes itself as an MCP server for other agents.",
"hermes auth add lets you add multiple API keys for credential pool rotation.",
"hermes completion bash >> ~/.bashrc enables tab completion for all commands and profiles.",
"hermes logs -f follows agent.log in real time. --level WARNING --since 1h filters output.",
"hermes backup creates a zip backup of your entire Hermes home directory.",
"hermes profile create coder creates an isolated profile that becomes its own command.",
"hermes profile create work --clone copies your current config and keys to a new profile.",
"hermes update syncs new bundled skills to ALL profiles automatically.",
"hermes gateway install sets up Hermes as a system service (systemd/launchd).",
"hermes memory setup lets you configure an external memory provider (Honcho, Mem0, etc.).",
"hermes webhook subscribe creates event-driven webhook routes with HMAC validation.",
# --- Configuration ---
"Set display.bell_on_complete: true in config.yaml to hear a bell when long tasks finish.",
"Set display.streaming: true to see tokens appear in real time as the model generates.",
"Set display.show_reasoning: true to watch the model's chain-of-thought reasoning.",
"Set display.compact: true to reduce whitespace in output for denser information.",
"Set display.busy_input_mode: queue to queue messages instead of interrupting the agent.",
"Set display.resume_display: minimal to skip the full conversation recap on session resume.",
"Set compression.threshold: 0.50 to control when auto-compression fires (default: 50% of context).",
"Set agent.max_turns: 200 to let the agent take more tool-calling steps per turn.",
"Set file_read_max_chars: 200000 to increase the max content per read_file call.",
"Set approvals.mode: smart to let an LLM auto-approve safe commands and auto-deny dangerous ones.",
"Set fallback_model in config.yaml to automatically fail over to a backup provider.",
"Set privacy.redact_pii: true to hash user IDs and phone numbers before sending to the LLM.",
"Set browser.record_sessions: true to auto-record browser sessions as WebM videos.",
"Set worktree: true in config.yaml to always create a git worktree (same as hermes -w).",
"Set security.website_blocklist.enabled: true to block specific domains from web tools.",
"Set cron.wrap_response: false to deliver raw agent output without the cron header/footer.",
"HERMES_TIMEZONE overrides the server timezone with any IANA timezone string.",
"Environment variable substitution works in config.yaml: use ${VAR_NAME} syntax.",
"Quick commands in config.yaml run shell commands instantly with zero token usage.",
"Custom personalities can be defined in config.yaml under agent.personalities.",
"provider_routing controls OpenRouter provider sorting, whitelisting, and blacklisting.",
# --- Tools & Capabilities ---
"execute_code runs Python scripts that call Hermes tools programmatically — results stay out of context.",
"delegate_task spawns up to 3 concurrent sub-agents with isolated contexts for parallel work.",
"web_extract works on PDF URLs — pass any PDF link and it converts to markdown.",
"search_files is ripgrep-backed and faster than grep — use it instead of terminal grep.",
"patch uses 9 fuzzy matching strategies so minor whitespace differences won't break edits.",
"patch supports V4A format for bulk multi-file edits in a single call.",
"read_file suggests similar filenames when a file isn't found.",
"read_file auto-deduplicates — re-reading an unchanged file returns a lightweight stub.",
"browser_vision takes a screenshot and analyzes it with AI — works for CAPTCHAs and visual content.",
"browser_console can evaluate JavaScript expressions in the page context.",
"image_generate creates images with FLUX 2 Pro and automatic 2x upscaling.",
"text_to_speech converts text to audio — plays as voice bubbles on Telegram.",
"send_message can reach any connected messaging platform from within a session.",
"The todo tool helps the agent track complex multi-step tasks during a session.",
"session_search performs full-text search across ALL past conversations.",
"The agent automatically saves preferences, corrections, and environment facts to memory.",
"mixture_of_agents routes hard problems through 4 frontier LLMs collaboratively.",
"Terminal commands support background mode with notify_on_complete for long-running tasks.",
"Terminal background processes support watch_patterns to alert on specific output lines.",
"The terminal tool supports 6 backends: local, Docker, SSH, Modal, Daytona, and Singularity.",
# --- Profiles ---
"Each profile gets its own config, API keys, memory, sessions, skills, and cron jobs.",
"Profile names become shell commands — 'hermes profile create coder' creates the 'coder' command.",
"hermes profile export coder -o backup.tar.gz creates a portable profile archive.",
"If two profiles accidentally share a bot token, the second gateway is blocked with a clear error.",
# --- Sessions ---
"Sessions auto-generate descriptive titles after the first exchange — no manual naming needed.",
"Session titles support lineage: \"my project\"\"my project #2\"\"my project #3\".",
"When exiting, Hermes prints a resume command with session ID and stats.",
"hermes sessions export backup.jsonl exports all sessions for backup or analysis.",
"hermes -r SESSION_ID resumes any specific past session by its ID.",
# --- Memory ---
"Memory is a frozen snapshot — changes appear in the system prompt only at next session start.",
"Memory entries are automatically scanned for prompt injection and exfiltration patterns.",
"The agent has two memory stores: personal notes (~2200 chars) and user profile (~1375 chars).",
"Corrections you give the agent (\"no, do it this way\") are often auto-saved to memory.",
# --- Skills ---
"Over 80 bundled skills covering github, creative, mlops, productivity, research, and more.",
"Every installed skill automatically becomes a slash command — type / to see them all.",
"hermes skills install official/security/1password installs optional skills from the repo.",
"Skills can restrict to specific OS platforms — some only load on macOS or Linux.",
"skills.external_dirs in config.yaml lets you load skills from custom directories.",
"The agent can create its own skills as procedural memory using skill_manage.",
"The plan skill saves markdown plans under .hermes/plans/ in the active workspace.",
# --- Cron & Scheduling ---
"Cron jobs can attach skills: hermes cron add --skill blogwatcher \"Check for new posts\".",
"Cron delivery targets include telegram, discord, slack, email, sms, and 12+ more platforms.",
"If a cron response starts with [SILENT], delivery is suppressed — useful for monitoring-only jobs.",
"Cron supports relative delays (30m), intervals (every 2h), cron expressions, and ISO timestamps.",
"Cron jobs run in completely fresh agent sessions — prompts must be self-contained.",
# --- Voice ---
"Voice mode works with zero API keys if faster-whisper is installed (free local speech-to-text).",
"Five TTS providers available: Edge TTS (free), ElevenLabs, OpenAI, NeuTTS (free local), MiniMax.",
"/voice on enables voice mode in the CLI. Ctrl+B toggles push-to-talk recording.",
"Streaming TTS plays sentences as they generate — you don't wait for the full response.",
"Voice messages on Telegram, Discord, WhatsApp, and Slack are auto-transcribed.",
# --- Gateway & Messaging ---
"Hermes runs on 18 platforms: Telegram, Discord, Slack, WhatsApp, Signal, Matrix, email, and more.",
"hermes gateway install sets it up as a system service that starts on boot.",
"DingTalk uses Stream Mode — no webhooks or public URL needed.",
"BlueBubbles brings iMessage to Hermes via a local macOS server.",
"Webhook routes support HMAC validation, rate limiting, and event filtering.",
"The API server exposes an OpenAI-compatible endpoint compatible with Open WebUI and LibreChat.",
"Discord voice channel mode: the bot joins VC, transcribes speech, and talks back.",
"group_sessions_per_user: true gives each person their own session in group chats.",
"/sethome marks a chat as the home channel for cron job deliveries.",
"The gateway supports inactivity-based timeouts — active agents can run indefinitely.",
# --- Security ---
"Dangerous command approval has 4 tiers: once, session, always (permanent allowlist), deny.",
"Smart approval mode uses an LLM to auto-approve safe commands and flag dangerous ones.",
"SSRF protection blocks private networks, loopback, link-local, and cloud metadata addresses.",
"Tirith pre-exec scanning detects homograph URL spoofing and pipe-to-interpreter patterns.",
"MCP subprocesses receive a filtered environment — only safe system vars pass through.",
"Context files (.hermes.md, AGENTS.md) are security-scanned for prompt injection before loading.",
"command_allowlist in config.yaml permanently approves specific shell command patterns.",
# --- Context & Compression ---
"Context auto-compresses when it reaches the threshold — memories are flushed and history summarized.",
"The status bar turns yellow, then orange, then red as context fills up.",
"SOUL.md at ~/.hermes/SOUL.md is the agent's primary identity — customize it to shape behavior.",
"Hermes loads project context from .hermes.md, AGENTS.md, CLAUDE.md, or .cursorrules (first match).",
"Subdirectory AGENTS.md files are discovered progressively as the agent navigates into folders.",
"Context files are capped at 20,000 characters with smart head/tail truncation.",
# --- Browser ---
"Five browser providers: local Chromium, Browserbase, Browser Use, Camofox, and Firecrawl.",
"Camofox is an anti-detection browser — Firefox fork with C++ fingerprint spoofing.",
"browser_navigate returns a page snapshot automatically — no need to call browser_snapshot after.",
"browser_vision with annotate=true overlays numbered labels on interactive elements.",
# --- MCP ---
"MCP servers are configured in config.yaml — both stdio and HTTP transports supported.",
"Per-server tool filtering: tools.include whitelists and tools.exclude blacklists specific tools.",
"MCP servers auto-generate toolsets at runtime — hermes tools can toggle them per platform.",
"MCP OAuth support: auth: oauth enables browser-based authorization with PKCE.",
# --- Checkpoints & Rollback ---
"Checkpoints have zero overhead when no files are modified — enabled by default.",
"A pre-rollback snapshot is saved automatically so you can undo the undo.",
"/rollback also undoes the conversation turn, so the agent doesn't remember rolled-back changes.",
"Checkpoints use shadow repos in ~/.hermes/checkpoints/ — your project's .git is never touched.",
# --- Batch & Data ---
"batch_runner.py processes hundreds of prompts in parallel for training data generation.",
"hermes chat -Q enables quiet mode for programmatic use — suppresses banner and spinner.",
"Trajectory saving (--save-trajectories) captures full tool-use traces for model training.",
# --- Plugins ---
"Three plugin types: general (tools/hooks), memory providers, and context engines.",
"hermes plugins install owner/repo installs plugins directly from GitHub.",
"8 external memory providers available: Honcho, OpenViking, Mem0, Hindsight, and more.",
"Plugin hooks include pre_tool_call, post_tool_call, pre_llm_call, and post_llm_call.",
# --- Miscellaneous ---
"Prompt caching (Anthropic) reduces costs by reusing cached system prompt prefixes.",
"The agent auto-generates session titles in a background thread — zero latency impact.",
"Smart model routing can auto-route simple queries to a cheaper model.",
"Slash commands support prefix matching: /h resolves to /help, /mod to /model.",
"Dragging a file path into the terminal auto-attaches images or sends as context.",
".worktreeinclude in your repo root lists gitignored files to copy into worktrees.",
"hermes acp runs Hermes as an ACP server for VS Code, Zed, and JetBrains integration.",
"Custom providers: save named endpoints in config.yaml under custom_providers.",
"HERMES_EPHEMERAL_SYSTEM_PROMPT injects a system prompt that's never persisted to history.",
"credential_pool_strategies supports fill_first, round_robin, least_used, and random rotation.",
"hermes login supports OAuth-based auth for Nous and OpenAI Codex providers.",
"The API server supports both Chat Completions and Responses API with server-side state.",
"tool_preview_length: 0 in config shows full file paths in the spinner's activity feed.",
"hermes status --deep runs deeper diagnostic checks across all components.",
]
def get_random_tip(exclude_recent: int = 0) -> str:
"""Return a random tip string.
Args:
exclude_recent: not used currently; reserved for future
deduplication across sessions.
"""
return random.choice(TIPS)
def get_tip_count() -> int:
"""Return the total number of tips available."""
return len(TIPS)

View file

@ -0,0 +1,77 @@
"""Tests for hermes_cli/tips.py — random tip display at session start."""
import pytest
from hermes_cli.tips import TIPS, get_random_tip, get_tip_count
class TestTipsCorpus:
"""Validate the tip corpus itself."""
def test_has_at_least_200_tips(self):
assert len(TIPS) >= 200, f"Expected 200+ tips, got {len(TIPS)}"
def test_no_duplicates(self):
assert len(TIPS) == len(set(TIPS)), "Duplicate tips found"
def test_all_tips_are_strings(self):
for i, tip in enumerate(TIPS):
assert isinstance(tip, str), f"Tip {i} is not a string: {type(tip)}"
def test_no_empty_tips(self):
for i, tip in enumerate(TIPS):
assert tip.strip(), f"Tip {i} is empty or whitespace-only"
def test_max_length_reasonable(self):
"""Tips should fit on a single terminal line (~120 chars max)."""
for i, tip in enumerate(TIPS):
assert len(tip) <= 150, (
f"Tip {i} too long ({len(tip)} chars): {tip[:60]}..."
)
def test_no_leading_trailing_whitespace(self):
for i, tip in enumerate(TIPS):
assert tip == tip.strip(), f"Tip {i} has leading/trailing whitespace"
class TestGetRandomTip:
"""Validate the get_random_tip() function."""
def test_returns_string(self):
tip = get_random_tip()
assert isinstance(tip, str)
assert len(tip) > 0
def test_returns_tip_from_corpus(self):
tip = get_random_tip()
assert tip in TIPS
def test_randomness(self):
"""Multiple calls should eventually return different tips."""
seen = set()
for _ in range(50):
seen.add(get_random_tip())
# With 200+ tips and 50 draws, we should see at least 10 unique
assert len(seen) >= 10, f"Only got {len(seen)} unique tips in 50 draws"
class TestGetTipCount:
def test_matches_corpus_length(self):
assert get_tip_count() == len(TIPS)
class TestTipIntegrationInCLI:
"""Test that the tip display code in cli.py works correctly."""
def test_tip_import_works(self):
"""The import used in cli.py must succeed."""
from hermes_cli.tips import get_random_tip
assert callable(get_random_tip)
def test_tip_display_format(self):
"""Verify the Rich markup format doesn't break."""
tip = get_random_tip()
color = "#B8860B"
markup = f"[dim {color}]✦ Tip: {tip}[/]"
# Should not contain nested/broken Rich tags
assert markup.count("[/]") == 1
assert "[dim #B8860B]" in markup