2026-02-21 23:17:18 -08:00
|
|
|
"""Slash command definitions and autocomplete for the Hermes CLI.
|
|
|
|
|
|
2026-03-07 17:53:41 -08:00
|
|
|
Contains the shared built-in ``COMMANDS`` dict and ``SlashCommandCompleter``.
|
|
|
|
|
The completer can optionally include dynamic skill slash commands supplied by the
|
|
|
|
|
interactive CLI.
|
2026-02-21 23:17:18 -08:00
|
|
|
"""
|
|
|
|
|
|
2026-03-07 17:53:41 -08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from collections.abc import Callable, Mapping
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
2026-02-21 23:17:18 -08:00
|
|
|
from prompt_toolkit.completion import Completer, Completion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COMMANDS = {
|
|
|
|
|
"/help": "Show this help message",
|
|
|
|
|
"/tools": "List available tools",
|
|
|
|
|
"/toolsets": "List available toolsets",
|
|
|
|
|
"/model": "Show or change the current model",
|
2026-03-08 06:09:36 -07:00
|
|
|
"/provider": "Show available providers and current provider",
|
2026-02-21 23:17:18 -08:00
|
|
|
"/prompt": "View/set custom system prompt",
|
|
|
|
|
"/personality": "Set a predefined personality",
|
|
|
|
|
"/clear": "Clear screen and reset conversation (fresh start)",
|
|
|
|
|
"/history": "Show conversation history",
|
|
|
|
|
"/new": "Start a new conversation (reset history)",
|
|
|
|
|
"/reset": "Reset conversation only (keep screen)",
|
|
|
|
|
"/retry": "Retry the last message (resend to agent)",
|
|
|
|
|
"/undo": "Remove the last user/assistant exchange",
|
|
|
|
|
"/save": "Save the current conversation",
|
|
|
|
|
"/config": "Show current configuration",
|
|
|
|
|
"/cron": "Manage scheduled tasks (list, add, remove)",
|
|
|
|
|
"/skills": "Search, install, inspect, or manage skills from online registries",
|
|
|
|
|
"/platforms": "Show gateway/messaging platform status",
|
2026-02-28 00:05:58 -08:00
|
|
|
"/verbose": "Cycle tool progress display: off → new → all → verbose",
|
2026-03-01 00:16:38 -08:00
|
|
|
"/compress": "Manually compress conversation context (flush memories + summarize)",
|
2026-03-08 15:20:29 -07:00
|
|
|
"/title": "Set a title for the current session (usage: /title My Session Name)",
|
2026-03-01 00:23:19 -08:00
|
|
|
"/usage": "Show token usage for the current session",
|
feat: add /insights command with usage analytics and cost estimation
Inspired by Claude Code's /insights, adapted for Hermes Agent's multi-platform
architecture. Analyzes session history from state.db to produce comprehensive
usage insights.
Features:
- Overview stats: sessions, messages, tokens, estimated cost, active time
- Model breakdown: per-model sessions, tokens, and cost estimation
- Platform breakdown: CLI vs Telegram vs Discord etc. (unique to Hermes)
- Tool usage ranking: most-used tools with percentages
- Activity patterns: day-of-week chart, peak hours, streaks
- Notable sessions: longest, most messages, most tokens, most tool calls
- Cost estimation: real pricing data for 25+ models (OpenAI, Anthropic,
DeepSeek, Google, Meta) with fuzzy model name matching
- Configurable time window: --days flag (default 30)
- Source filtering: --source flag to filter by platform
Three entry points:
- /insights slash command in CLI (supports --days and --source flags)
- /insights slash command in gateway (compact markdown format)
- hermes insights CLI subcommand (standalone)
Includes 56 tests covering pricing helpers, format helpers, empty DB,
populated DB with multi-platform data, filtering, formatting, and edge cases.
2026-03-06 14:04:59 -08:00
|
|
|
"/insights": "Show usage insights and analytics (last 30 days)",
|
2026-03-07 17:53:41 -08:00
|
|
|
"/paste": "Check clipboard for an image and attach it",
|
|
|
|
|
"/reload-mcp": "Reload MCP servers from config.yaml",
|
feat: add data-driven skin/theme engine for CLI customization
Adds a skin system that lets users customize the CLI's visual appearance
through data files (YAML) rather than code changes. Skins define: color
palette, spinner faces/verbs/wings, branding text, and tool output prefix.
New files:
- hermes_cli/skin_engine.py — SkinConfig dataclass, built-in skins
(default, ares, mono, slate), YAML loader for user skins from
~/.hermes/skins/, skin management API
- tests/hermes_cli/test_skin_engine.py — 26 tests covering config,
built-in skins, user YAML skins, display integration
Modified files:
- agent/display.py — skin-aware spinner wings, faces, verbs, tool prefix
- hermes_cli/banner.py — skin-aware banner colors (title, border, accent,
dim, text, session) via _skin_color()/_skin_branding() helpers
- cli.py — /skin command handler, skin init from config, skin-aware
response box label and welcome message
- hermes_cli/config.py — add display.skin default
- hermes_cli/commands.py — add /skin to slash commands
Built-in skins:
- default: classic Hermes gold/kawaii
- ares: crimson/bronze war-god theme (from community PRs #579/#725)
- mono: clean grayscale
- slate: cool blue developer theme
User skins: drop a YAML file in ~/.hermes/skins/ with name, colors,
spinner, branding, and tool_prefix fields. Missing values inherit from
the default skin.
2026-03-10 00:37:28 -07:00
|
|
|
"/rollback": "List or restore filesystem checkpoints (usage: /rollback [number])",
|
|
|
|
|
"/skin": "Show or change the display skin/theme",
|
2026-02-21 23:17:18 -08:00
|
|
|
"/quit": "Exit the CLI (also: /exit, /q)",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SlashCommandCompleter(Completer):
|
2026-03-07 17:53:41 -08:00
|
|
|
"""Autocomplete for built-in slash commands and optional skill commands."""
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
skill_commands_provider: Callable[[], Mapping[str, dict[str, Any]]] | None = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
self._skill_commands_provider = skill_commands_provider
|
|
|
|
|
|
|
|
|
|
def _iter_skill_commands(self) -> Mapping[str, dict[str, Any]]:
|
|
|
|
|
if self._skill_commands_provider is None:
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
return self._skill_commands_provider() or {}
|
|
|
|
|
except Exception:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _completion_text(cmd_name: str, word: str) -> str:
|
|
|
|
|
"""Return replacement text for a completion.
|
|
|
|
|
|
|
|
|
|
When the user has already typed the full command exactly (``/help``),
|
|
|
|
|
returning ``help`` would be a no-op and prompt_toolkit suppresses the
|
|
|
|
|
menu. Appending a trailing space keeps the dropdown visible and makes
|
|
|
|
|
backspacing retrigger it naturally.
|
|
|
|
|
"""
|
|
|
|
|
return f"{cmd_name} " if cmd_name == word else cmd_name
|
2026-02-21 23:17:18 -08:00
|
|
|
|
|
|
|
|
def get_completions(self, document, complete_event):
|
|
|
|
|
text = document.text_before_cursor
|
|
|
|
|
if not text.startswith("/"):
|
|
|
|
|
return
|
2026-03-07 17:53:41 -08:00
|
|
|
|
2026-02-21 23:17:18 -08:00
|
|
|
word = text[1:]
|
2026-03-07 17:53:41 -08:00
|
|
|
|
2026-02-21 23:17:18 -08:00
|
|
|
for cmd, desc in COMMANDS.items():
|
|
|
|
|
cmd_name = cmd[1:]
|
|
|
|
|
if cmd_name.startswith(word):
|
|
|
|
|
yield Completion(
|
2026-03-07 17:53:41 -08:00
|
|
|
self._completion_text(cmd_name, word),
|
2026-02-21 23:17:18 -08:00
|
|
|
start_position=-len(word),
|
|
|
|
|
display=cmd,
|
|
|
|
|
display_meta=desc,
|
|
|
|
|
)
|
2026-03-07 17:53:41 -08:00
|
|
|
|
|
|
|
|
for cmd, info in self._iter_skill_commands().items():
|
|
|
|
|
cmd_name = cmd[1:]
|
|
|
|
|
if cmd_name.startswith(word):
|
|
|
|
|
description = str(info.get("description", "Skill command"))
|
|
|
|
|
short_desc = description[:50] + ("..." if len(description) > 50 else "")
|
|
|
|
|
yield Completion(
|
|
|
|
|
self._completion_text(cmd_name, word),
|
|
|
|
|
start_position=-len(word),
|
|
|
|
|
display=cmd,
|
|
|
|
|
display_meta=f"⚡ {short_desc}",
|
|
|
|
|
)
|