fix(branding): use the Caduceus ☤ (U+2624), not the Rod of Asclepius ⚕ (U+2625)
Every inline glyph — CLI banner/status bar/response labels/goodbye, setup and doctor boxes, gateway update prompts, WhatsApp reply prefix, TUI theme, locale strings and the docs — used ⚕, the staff of Asclepius (medicine). Hermes carries the Caduceus ☤. The ASCII-art logo was already correct. Mechanical swap across 60 files (no logic change); both glyphs are East-Asian-width Neutral so no layout shifts. Skins that set their own `response_label` / `goodbye` are unaffected. Direction from PR #7064 (@bixycler), the earliest of #7064 / #9611 / #15574, redone against current main. Fixes #9565
This commit is contained in:
@@ -2297,7 +2297,7 @@ def _build_compact_banner() -> str:
|
||||
dim_color = _color("banner_dim", "#B8860B")
|
||||
|
||||
if (getattr(_skin, "name", "default") if _skin else "default") == "default":
|
||||
tiny_line = "⚕ NOUS HERMES"
|
||||
tiny_line = "☤ NOUS HERMES"
|
||||
else:
|
||||
tiny_line = _skin.get_branding("agent_name", "Hermes Agent") if _skin else "Hermes Agent"
|
||||
line1 = f"{tiny_line} - AI Agent Framework"
|
||||
|
||||
@@ -1499,7 +1499,7 @@ class QQAdapter(BasePlatformAdapter):
|
||||
are written to ``~/.hermes/.update_response`` by the interaction callback."""
|
||||
del session_key, metadata # present for contract parity only.
|
||||
default_hint = f" (default: {default})" if default else ""
|
||||
content = f"⚕ **Update Needs Your Input**\n\n{prompt}{default_hint}"
|
||||
content = f"☤ **Update Needs Your Input**\n\n{prompt}{default_hint}"
|
||||
return await self.send_with_keyboard(
|
||||
chat_id, content, build_update_prompt_keyboard(), reply_to=self._last_msg_id.get(chat_id)
|
||||
)
|
||||
|
||||
@@ -55,7 +55,7 @@ class WhatsAppBehaviorMixin:
|
||||
MAX_MESSAGE_LENGTH: int = 4096
|
||||
supports_code_blocks = True # WhatsApp renders fenced code blocks (monospace)
|
||||
|
||||
DEFAULT_REPLY_PREFIX: str = "⚕ *Hermes Agent*\n────────────\n"
|
||||
DEFAULT_REPLY_PREFIX: str = "☤ *Hermes Agent*\n────────────\n"
|
||||
|
||||
_OUTBOUND_INVISIBLE_CHARS_RE = re.compile(r"[\u200b\u2060\u2063\ufeff]")
|
||||
_OUTBOUND_ODD_SPACE_RE = re.compile(r"[\u00a0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]")
|
||||
|
||||
@@ -529,7 +529,7 @@ class GatewayNotificationsMixin:
|
||||
default_hint = f" (default: {default})" if default else ""
|
||||
_p = getattr(adapter, "typed_command_prefix", "/")
|
||||
await target.send(
|
||||
f"⚕ **Update needs your input:**\n\n{prompt_text}{default_hint}\n\n"
|
||||
f"☤ **Update needs your input:**\n\n{prompt_text}{default_hint}\n\n"
|
||||
f"Reply `{_p}approve` (yes) or `{_p}deny` (no), or type your answer directly."
|
||||
)
|
||||
# Keep the prompt marker on disk until answered so a restarted watcher can re-forward it.
|
||||
|
||||
@@ -10,7 +10,7 @@ __release_date__ = "2026.9.11"
|
||||
def _ensure_utf8():
|
||||
"""Force UTF-8 stdout/stderr to prevent UnicodeEncodeError crashes.
|
||||
|
||||
The CLI prints box-drawing characters and the ⚕ glyph in the setup wizard, doctor, and status
|
||||
The CLI prints box-drawing characters and the ☤ glyph in the setup wizard, doctor, and status
|
||||
banners; under a non-UTF-8 codec that raises before the command can even start (e.g.
|
||||
`hermes setup` on a fresh Pi).
|
||||
"""
|
||||
|
||||
@@ -535,7 +535,7 @@ def import_agent_command(args) -> None:
|
||||
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.MAGENTA))
|
||||
print(color("│ ⚕ Hermes — Import From Another Agent │", Colors.MAGENTA))
|
||||
print(color("│ ☤ Hermes — Import From Another Agent │", Colors.MAGENTA))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.MAGENTA))
|
||||
if not source_dir.is_dir():
|
||||
print()
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ def _print_banner(title: str) -> None:
|
||||
"""Print the magenta boxed banner shared by the claw subcommands."""
|
||||
print()
|
||||
rule = "─" * 57
|
||||
for line in (f"┌{rule}┐", f"│ ⚕ Hermes — {title:<35s}│", f"└{rule}┘"):
|
||||
for line in (f"┌{rule}┐", f"│ ☤ Hermes — {title:<35s}│", f"└{rule}┘"):
|
||||
print(color(line, Colors.MAGENTA))
|
||||
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ class CLIAgentSetupMixin:
|
||||
source of truth. True when a provider was configured."""
|
||||
from cli import _cprint, logger
|
||||
_cprint("")
|
||||
_cprint("⚕ No inference provider is configured yet — let's fix that.")
|
||||
_cprint("☤ No inference provider is configured yet — let's fix that.")
|
||||
_cprint(" You'll pick a provider (Nous Portal OAuth is the fastest; "
|
||||
"no API key needed) and a model.")
|
||||
try:
|
||||
|
||||
@@ -243,7 +243,7 @@ class CLIBillingMixin:
|
||||
self._print_logged_out(state, "Could not load subscription", "/subscription")
|
||||
return
|
||||
if state.context == "team": # no personal plan — teams run on a shared balance
|
||||
self._block_header("⚕", "Team subscription")
|
||||
self._block_header("☤", "Team subscription")
|
||||
self._print_org_line(state)
|
||||
print(f" This terminal is connected to {state.org_name or 'a team org'}. Teams run on a shared")
|
||||
print(" balance · use /topup to add funds.")
|
||||
@@ -288,7 +288,7 @@ class CLIBillingMixin:
|
||||
_cprint(f" {_from} ──▶ {_to} {_d('· ' + _when)}")
|
||||
self._dim(f"You keep {_from} (and its credits) until then.")
|
||||
_cprint("")
|
||||
_cprint(f" ⚕ {_b(status)}")
|
||||
_cprint(f" ☤ {_b(status)}")
|
||||
print(f" {_RULE}")
|
||||
for _bar_ln in self._usage_bar_lines(usage, plan_name):
|
||||
print(_bar_ln)
|
||||
@@ -321,7 +321,7 @@ class CLIBillingMixin:
|
||||
if not tiers:
|
||||
self._subscription_open_portal(state, manage_url, verb="Start a subscription")
|
||||
return
|
||||
self._block_header("⚕", "Choose a plan")
|
||||
self._block_header("☤", "Choose a plan")
|
||||
for i, t in enumerate(tiers, 1):
|
||||
print(f" {i}. {format_tier_row(t)}")
|
||||
self._dim('Starting a subscription opens the portal to add your card.')
|
||||
|
||||
@@ -243,7 +243,7 @@ class CLIChatTurnMixin:
|
||||
def display_callback(sentence: str):
|
||||
if not turn.box_opened:
|
||||
turn.box_opened = True
|
||||
label = " ⚕ Hermes "
|
||||
label = " ☤ Hermes "
|
||||
if self.show_timestamps:
|
||||
label = f"{label}{datetime.now().strftime(self.timestamp_format)} "
|
||||
w = self._scrollback_box_width(getattr(self.console, "width", 80))
|
||||
@@ -605,11 +605,11 @@ class CLIChatTurnMixin:
|
||||
try:
|
||||
from hermes_cli.skin_engine import get_active_skin
|
||||
_skin = get_active_skin()
|
||||
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||
label = _skin.get_branding("response_label", "☤ Hermes")
|
||||
_resp_color = _maybe_remap_for_light_mode(_skin.get_color("response_border", "#CD7F32"))
|
||||
_resp_text = _maybe_remap_for_light_mode(_skin.get_color("banner_text", "#FFF8DC"))
|
||||
except Exception:
|
||||
label = "⚕ Hermes"
|
||||
label = "☤ Hermes"
|
||||
_resp_color = _maybe_remap_for_light_mode("#CD7F32")
|
||||
_resp_text = _maybe_remap_for_light_mode("#FFF8DC")
|
||||
|
||||
|
||||
@@ -378,11 +378,11 @@ def _print_side_result_panel(cli, *, header_lines, body, title_suffix, empty_not
|
||||
try:
|
||||
from hermes_cli.skin_engine import get_active_skin
|
||||
_skin = get_active_skin()
|
||||
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||
label = _skin.get_branding("response_label", "☤ Hermes")
|
||||
_resp_color = _maybe_remap_for_light_mode(_skin.get_color("response_border", "#CD7F32"))
|
||||
_resp_text = _maybe_remap_for_light_mode(_skin.get_color("banner_text", "#FFF8DC"))
|
||||
except Exception:
|
||||
label, _resp_color, _resp_text = "⚕ Hermes", "#CD7F32", "#FFF8DC"
|
||||
label, _resp_color, _resp_text = "☤ Hermes", "#CD7F32", "#FFF8DC"
|
||||
rich_console.print(Panel(
|
||||
_render_final_assistant_content(body, mode=cli.final_response_markdown),
|
||||
title=f"[{_resp_color} bold]{label} {title_suffix}[/]", title_align="left",
|
||||
@@ -2654,12 +2654,12 @@ class CLICommandsMixin:
|
||||
choices = [("once", "Update Now", "exit the current session and update Hermes Agent"),
|
||||
("cancel", "Cancel", "keep the current session")]
|
||||
raw = self._prompt_text_input_modal(
|
||||
title="⚕ Update Hermes Agent",
|
||||
title="☤ Update Hermes Agent",
|
||||
detail="This will exit the current session and run `hermes update`.", choices=choices)
|
||||
if raw is None or self._normalize_slash_confirm_choice(raw, choices) != "once":
|
||||
print(" 🟡 /update cancelled.")
|
||||
return False
|
||||
_say_block(" ⚕ Launching update...")
|
||||
_say_block(" ☤ Launching update...")
|
||||
# run() execs this on the main thread after prompt_toolkit restores terminal modes;
|
||||
# relaunching from this daemon thread would skip cleanup (POSIX) / only end the thread (Windows).
|
||||
self._pending_relaunch = ["update"]
|
||||
|
||||
@@ -1291,9 +1291,9 @@ class CLISessionMixin:
|
||||
if not msg_count:
|
||||
try:
|
||||
from hermes_cli.skin_engine import get_active_goodbye
|
||||
goodbye = get_active_goodbye("Goodbye! ⚕")
|
||||
goodbye = get_active_goodbye("Goodbye! ☤")
|
||||
except Exception:
|
||||
goodbye = "Goodbye! ⚕"
|
||||
goodbye = "Goodbye! ☤"
|
||||
print(goodbye)
|
||||
return
|
||||
|
||||
|
||||
@@ -1001,9 +1001,9 @@ class CLIStatusBarMixin:
|
||||
|
||||
if _ok("model"):
|
||||
if styled:
|
||||
segs.append([(_SB, " ⚕ "), (_STRONG, model_short)])
|
||||
segs.append([(_SB, " ☤ "), (_STRONG, model_short)])
|
||||
else:
|
||||
segs.append([("", f"⚕ {model_short}")])
|
||||
segs.append([("", f"☤ {model_short}")])
|
||||
narrow, wide = width < 52, width >= 76
|
||||
if narrow:
|
||||
# Narrow bars put duration ahead of the goal segment; the other tiers reverse it.
|
||||
@@ -1074,7 +1074,7 @@ class CLIStatusBarMixin:
|
||||
session_title = (snapshot.get("session_title") or "") if show_title else ""
|
||||
segs = self._status_bar_segments(
|
||||
snapshot, width, field_set, self._is_session_yolo_active(), styled=False)
|
||||
parts = ["".join(t for _, t in seg) for seg in segs] or [f"⚕ {model_short}"]
|
||||
parts = ["".join(t for _, t in seg) for seg in segs] or [f"☤ {model_short}"]
|
||||
# Narrow bars always join the battery with │; wider tiers use the tier separator.
|
||||
if battery_label:
|
||||
parts.insert(0, battery_label)
|
||||
@@ -1084,7 +1084,7 @@ class CLIStatusBarMixin:
|
||||
text = (" · " if width < 76 else " │ ").join(parts)
|
||||
return self._right_align_status_title(text, session_title, width)
|
||||
except Exception:
|
||||
return f"⚕ {self.model if getattr(self, 'model', None) else 'Hermes'}"
|
||||
return f"☤ {self.model if getattr(self, 'model', None) else 'Hermes'}"
|
||||
|
||||
def _get_status_bar_fragments(self):
|
||||
if (
|
||||
@@ -1107,7 +1107,7 @@ class CLIStatusBarMixin:
|
||||
snapshot, width, field_set, self._is_session_yolo_active(), styled=True)
|
||||
sep = " · " if width < 76 else " │ "
|
||||
frags: list = []
|
||||
for seg in segs or [[(_SB, " ⚕ "), (_STRONG, snapshot["model_short"])]]:
|
||||
for seg in segs or [[(_SB, " ☤ "), (_STRONG, snapshot["model_short"])]]:
|
||||
if frags:
|
||||
frags.append((_DIM, sep))
|
||||
frags.extend(seg)
|
||||
@@ -1120,7 +1120,7 @@ class CLIStatusBarMixin:
|
||||
if stash_indicator and _ok("stash"):
|
||||
frags.extend([(_DIM, " · "), (_STRONG, stash_indicator)])
|
||||
frags.append((_SB, " ")) # one-cell right margin
|
||||
# Battery is the first element when enabled: prepend ahead of the ⚕ marker.
|
||||
# Battery is the first element when enabled: prepend ahead of the ☤ marker.
|
||||
battery_label = snapshot.get("battery_label") or ""
|
||||
if battery_label and _ok("battery"):
|
||||
battery_style = self._battery_status_style(snapshot.get("battery_category", "dim"))
|
||||
|
||||
@@ -423,10 +423,10 @@ class CLIStreamMixin:
|
||||
try:
|
||||
from hermes_cli.skin_engine import get_active_skin
|
||||
_skin = get_active_skin()
|
||||
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||
label = _skin.get_branding("response_label", "☤ Hermes")
|
||||
_text_hex = _skin.get_color("banner_text", "#FFF8DC")
|
||||
except Exception:
|
||||
label = "⚕ Hermes"
|
||||
label = "☤ Hermes"
|
||||
_text_hex = "#FFF8DC"
|
||||
try: # true-color escape so streamed text matches the Rich Panel appearance
|
||||
_r, _g, _b = (int(_text_hex[i:i + 2], 16) for i in (1, 3, 5))
|
||||
|
||||
@@ -299,7 +299,7 @@ class CLITuiMixin:
|
||||
if self._command_running:
|
||||
return _state_fragment("class:prompt-working", self._command_spinner_frame())
|
||||
if self._agent_running:
|
||||
return _state_fragment("class:prompt-working", "⚕")
|
||||
return _state_fragment("class:prompt-working", "☤")
|
||||
if self._voice_mode:
|
||||
return _state_fragment("class:voice-prompt", "🎤")
|
||||
return [("class:prompt", symbol)]
|
||||
|
||||
@@ -2884,7 +2884,7 @@ def show_config():
|
||||
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.CYAN))
|
||||
print(color("│ ⚕ Hermes Configuration │", Colors.CYAN))
|
||||
print(color("│ ☤ Hermes Configuration │", Colors.CYAN))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.CYAN))
|
||||
_show_managed_banner()
|
||||
|
||||
|
||||
@@ -1493,7 +1493,7 @@ DEFAULT_CONFIG = {
|
||||
},
|
||||
|
||||
"whatsapp": {
|
||||
# reply_prefix: None = built-in "⚕ *Hermes Agent*" header; "" disables; \n allowed.
|
||||
# reply_prefix: None = built-in "☤ *Hermes Agent*" header; "" disables; \n allowed.
|
||||
},
|
||||
|
||||
"telegram": {
|
||||
|
||||
@@ -4743,7 +4743,7 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False, fo
|
||||
|
||||
from gateway.run import start_gateway
|
||||
print("┌─────────────────────────────────────────────────────────┐")
|
||||
print("│ ⚕ Hermes Gateway Starting... │")
|
||||
print("│ ☤ Hermes Gateway Starting... │")
|
||||
print("├─────────────────────────────────────────────────────────┤")
|
||||
print("│ Messaging platforms + cron scheduler │")
|
||||
print("│ Press Ctrl+C to stop │")
|
||||
@@ -5650,7 +5650,7 @@ def _setup_service_action(
|
||||
|
||||
_WIZARD_BANNER = (
|
||||
"┌─────────────────────────────────────────────────────────┐",
|
||||
"│ ⚕ Gateway Setup │",
|
||||
"│ ☤ Gateway Setup │",
|
||||
"├─────────────────────────────────────────────────────────┤",
|
||||
"│ Configure messaging platforms and the gateway service. │",
|
||||
"│ Press Ctrl+C at any time to exit. │",
|
||||
|
||||
@@ -128,7 +128,7 @@ def cmd_whatsapp(args):
|
||||
_require_tty("whatsapp")
|
||||
from hermes_cli.config import get_env_value, save_env_value
|
||||
from hermes_constants import find_node_executable, with_hermes_node_path
|
||||
_say("", "⚕ WhatsApp Setup", "=" * 50)
|
||||
_say("", "☤ WhatsApp Setup", "=" * 50)
|
||||
|
||||
wa_mode = _whatsapp_choose_mode(get_env_value, save_env_value)
|
||||
if wa_mode is None:
|
||||
@@ -192,12 +192,12 @@ def cmd_whatsapp(args):
|
||||
_say(" Next steps:", " 1. Start the gateway: hermes gateway",
|
||||
" 2. Send a message to the bot's WhatsApp number",
|
||||
" 3. The agent will reply automatically", "",
|
||||
" Tip: Agent responses are prefixed with '⚕ Hermes Agent'")
|
||||
" Tip: Agent responses are prefixed with '☤ Hermes Agent'")
|
||||
else:
|
||||
_say(" Next steps:", " 1. Start the gateway: hermes gateway",
|
||||
" 2. Open WhatsApp → Message Yourself",
|
||||
" 3. Type a message — the agent will reply", "",
|
||||
" Tip: Agent responses are prefixed with '⚕ Hermes Agent'",
|
||||
" Tip: Agent responses are prefixed with '☤ Hermes Agent'",
|
||||
" so you can tell them apart from your own messages.")
|
||||
_say("", " Or install as a service: hermes gateway install")
|
||||
|
||||
|
||||
@@ -815,7 +815,7 @@ def _launch_tui(
|
||||
# preserve_inherited=False keeps --tui and other flags out of the subcommand.
|
||||
if code == 42:
|
||||
from hermes_cli.relaunch import relaunch
|
||||
print("\n⚕ Launching update...\n")
|
||||
print("\n☤ Launching update...\n")
|
||||
relaunch(["update"], preserve_inherited=False)
|
||||
|
||||
sys.exit(code)
|
||||
|
||||
+3
-3
@@ -80,7 +80,7 @@ def is_interactive_stdin() -> bool:
|
||||
def print_noninteractive_setup_guidance(reason: str | None = None) -> None:
|
||||
"""Print guidance for headless/non-interactive setup flows."""
|
||||
print()
|
||||
print(color("⚕ Hermes Setup — Non-interactive mode", Colors.CYAN, Colors.BOLD))
|
||||
print(color("☤ Hermes Setup — Non-interactive mode", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
if reason:
|
||||
print_info(reason)
|
||||
@@ -588,7 +588,7 @@ def _run_setup_section(config: dict, section: str) -> None:
|
||||
print_info(f"Available sections: {', '.join(k for k, _, _ in SETUP_SECTIONS)}")
|
||||
return
|
||||
label, func = entry
|
||||
_print_banner(f"│ ⚕ Hermes Setup — {label:<34s} │")
|
||||
_print_banner(f"│ ☤ Hermes Setup — {label:<34s} │")
|
||||
_run_setup_steps([(label, lambda: func(config))])
|
||||
save_config(config)
|
||||
print()
|
||||
@@ -679,7 +679,7 @@ def _run_setup_wizard_impl(args):
|
||||
from hermes_cli.auth import get_active_provider
|
||||
is_existing = bool(get_env_value("OPENROUTER_API_KEY") or get_env_value("OPENAI_BASE_URL")
|
||||
or get_active_provider() is not None)
|
||||
_print_banner("│ ⚕ Hermes Agent Setup Wizard │",
|
||||
_print_banner("│ ☤ Hermes Agent Setup Wizard │",
|
||||
"├─────────────────────────────────────────────────────────┤",
|
||||
"│ Let's configure your Hermes Agent installation. │",
|
||||
"│ Press Ctrl+C at any time to exit. │")
|
||||
|
||||
@@ -57,7 +57,7 @@ def _run_nous_flow(config: dict, *, context: str, cancel_exc: tuple, cancel_line
|
||||
def _run_portal_one_shot(config: dict) -> None:
|
||||
"""One-shot Nous Portal setup (``hermes setup --portal`` / ``hermes portal``)."""
|
||||
from hermes_cli.setup import _info, _print_banner, print_error, print_info, print_success
|
||||
_print_banner("│ ⚕ Hermes Setup — Nous Portal (one-shot) │")
|
||||
_print_banner("│ ☤ Hermes Setup — Nous Portal (one-shot) │")
|
||||
_info(None, " One subscription, 300+ models, plus the Tool Gateway:",
|
||||
" web search, image generation, TTS, browser automation",
|
||||
" — all routed through your Nous Portal sub.", None,
|
||||
|
||||
@@ -293,7 +293,7 @@ def _step_allowlist() -> None:
|
||||
def run_whatsapp_cloud_setup() -> int:
|
||||
"""Interactive wizard for the WhatsApp Cloud API adapter. Returns 0 on success, 1 on abort."""
|
||||
_lines(
|
||||
"", "⚕ WhatsApp Business Cloud API Setup", "=" * 50, "",
|
||||
"", "☤ WhatsApp Business Cloud API Setup", "=" * 50, "",
|
||||
"This wizard configures Hermes to talk to WhatsApp via Meta's",
|
||||
"official Cloud API. It's the production-grade path:", "",
|
||||
" • No QR codes, no Node.js bridge subprocess",
|
||||
|
||||
@@ -57,7 +57,7 @@ def _wings(*glyphs) -> List[List[str]]:
|
||||
|
||||
# Branding shared by every Hermes-named built-in (mono/daylight override help_header).
|
||||
_HERMES_BRANDING: Dict[str, str] = _branding(
|
||||
"Hermes", "⚕", "Goodbye! ⚕", prompt="❯", help_header="(^_^)? Available Commands")
|
||||
"Hermes", "☤", "Goodbye! ☤", prompt="❯", help_header="(^_^)? Available Commands")
|
||||
|
||||
_BUILTIN_SKINS: Dict[str, Dict[str, Any]] = {
|
||||
"default": {
|
||||
@@ -489,7 +489,7 @@ def get_active_help_header(fallback: str = "(^_^)? Available Commands") -> str:
|
||||
return _active_branding("help_header", fallback)
|
||||
|
||||
|
||||
def get_active_goodbye(fallback: str = "Goodbye! ⚕") -> str:
|
||||
def get_active_goodbye(fallback: str = "Goodbye! ☤") -> str:
|
||||
return _active_branding("goodbye", fallback)
|
||||
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ def _banner(lines, *styles) -> None:
|
||||
|
||||
def _render_header(ctx):
|
||||
_banner(("┌─────────────────────────────────────────────────────────┐",
|
||||
"│ ⚕ Hermes Agent Status │",
|
||||
"│ ☤ Hermes Agent Status │",
|
||||
"└─────────────────────────────────────────────────────────┘"), Colors.CYAN)
|
||||
paused = _estop_status_line()
|
||||
if paused:
|
||||
|
||||
@@ -917,7 +917,7 @@ def _platform_menu_label(config: dict, pkey: str) -> str:
|
||||
def _print_tools_summary(config: dict, enabled_platforms: List[str]) -> None:
|
||||
"""``hermes tools --summary``: enabled toolsets per platform, non-interactive."""
|
||||
total = len(_get_effective_configurable_toolsets())
|
||||
print(color("⚕ Tool Summary", Colors.CYAN, Colors.BOLD))
|
||||
print(color("☤ Tool Summary", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
for pkey, enabled in _platform_toolset_summary(config, enabled_platforms).items():
|
||||
print(color(f" {PLATFORMS[pkey]['label']}", Colors.BOLD) + color(f" ({len(enabled)}/{total})", Colors.DIM))
|
||||
@@ -1024,7 +1024,7 @@ def tools_command(args=None, first_install: bool = False, config: dict = None):
|
||||
if getattr(args, "summary", False):
|
||||
_print_tools_summary(config, enabled_platforms)
|
||||
return
|
||||
print(color("⚕ Hermes Tool Configuration", Colors.CYAN, Colors.BOLD))
|
||||
print(color("☤ Hermes Tool Configuration", Colors.CYAN, Colors.BOLD))
|
||||
print(color(" Enable or disable tools per platform.", Colors.DIM))
|
||||
print(color(" Tools that need API keys will be configured when enabled.", Colors.DIM))
|
||||
print(color(" Guide: https://hermes-agent.nousresearch.com/docs/user-guide/features/tools", Colors.DIM))
|
||||
|
||||
@@ -425,7 +425,7 @@ def run_gui_uninstall(args):
|
||||
skip_confirm = bool(getattr(args, "yes", False))
|
||||
|
||||
print()
|
||||
_print_box("│ ⚕ Hermes Chat GUI Uninstaller │", Colors.MAGENTA)
|
||||
_print_box("│ ☤ Hermes Chat GUI Uninstaller │", Colors.MAGENTA)
|
||||
print()
|
||||
|
||||
if not summary["gui_installed"]:
|
||||
@@ -489,7 +489,7 @@ def run_uninstall(args):
|
||||
return
|
||||
|
||||
print()
|
||||
_print_box("│ ⚕ Hermes Agent Uninstaller │", Colors.MAGENTA)
|
||||
_print_box("│ ☤ Hermes Agent Uninstaller │", Colors.MAGENTA)
|
||||
print()
|
||||
|
||||
# Show what will be affected
|
||||
@@ -697,7 +697,7 @@ def _perform_uninstall(
|
||||
for line, col in _RELOAD_HINT[windows]:
|
||||
print(color(line, col) if col else line)
|
||||
print()
|
||||
print("Thank you for using Hermes Agent! ⚕")
|
||||
print("Thank you for using Hermes Agent! ☤")
|
||||
print()
|
||||
|
||||
|
||||
|
||||
@@ -588,9 +588,9 @@ def _print_update_check_result(behind: int | None, compare_branch: str) -> None:
|
||||
print("✓ Already up to date.")
|
||||
return
|
||||
if behind is not None:
|
||||
print(f"⚕ Update available: {behind} {'commit' if behind == 1 else 'commits'} behind {compare_branch}.")
|
||||
print(f"☤ Update available: {behind} {'commit' if behind == 1 else 'commits'} behind {compare_branch}.")
|
||||
else:
|
||||
print(f"⚕ Update available (behind {compare_branch}).")
|
||||
print(f"☤ Update available (behind {compare_branch}).")
|
||||
from hermes_cli.config import recommended_update_command
|
||||
print(f" Run '{recommended_update_command()}' to install.")
|
||||
|
||||
@@ -1282,7 +1282,7 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
||||
opts = _resolve_update_options(args, gateway_mode)
|
||||
gw_input_fn, assume_yes = opts.gw_input_fn, opts.assume_yes
|
||||
|
||||
print("⚕ Updating Hermes Agent...")
|
||||
print("☤ Updating Hermes Agent...")
|
||||
print()
|
||||
|
||||
_pre_update_plan = _begin_update_receipt_and_plan(args)
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Nie 'n git-bewaarplek nie — kan nie opdateer nie."
|
||||
hermes_cmd_not_found: "✗ Kon nie die `hermes`-opdrag vind nie. Hermes loop, maar die opdateeropdrag kon nie die uitvoerbare lêer op PATH of via die huidige Python-vertolker vind nie. Probeer `hermes update` met die hand in jou terminale uitvoer."
|
||||
start_failed: "✗ Kon nie opdatering begin nie: {error}"
|
||||
starting: "⚕ Begin Hermes-opdatering… Ek sal vordering hier stroom."
|
||||
starting: "☤ Begin Hermes-opdatering… Ek sal vordering hier stroom."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Tariefperke:** {state}"
|
||||
|
||||
+1
-1
@@ -389,7 +389,7 @@ gateway:
|
||||
not_git_repo: "✗ ليس مستودع git — لا يمكن التحديث."
|
||||
hermes_cmd_not_found: "✗ تعذّر تحديد موقع أمر `hermes`. Hermes يعمل، لكن أمر التحديث لم يجد الملف التنفيذي على PATH أو عبر مُفسّر Python الحالي. جرّب تشغيل `hermes update` يدويًا في طرفيتك."
|
||||
start_failed: "✗ فشل بدء التحديث: {error}"
|
||||
starting: "⚕ جارٍ بدء تحديث Hermes… سأبثّ التقدّم هنا."
|
||||
starting: "☤ جارٍ بدء تحديث Hermes… سأبثّ التقدّم هنا."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **حدود المعدّل:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Kein Git-Repository — Update nicht möglich."
|
||||
hermes_cmd_not_found: "✗ Der Befehl `hermes` konnte nicht gefunden werden. Hermes läuft, aber der Update-Befehl konnte das ausführbare Programm weder im PATH noch über den aktuellen Python-Interpreter finden. Versuchen Sie, `hermes update` manuell im Terminal auszuführen."
|
||||
start_failed: "✗ Update konnte nicht gestartet werden: {error}"
|
||||
starting: "⚕ Hermes-Update wird gestartet… Ich streame den Fortschritt hier."
|
||||
starting: "☤ Hermes-Update wird gestartet… Ich streame den Fortschritt hier."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Ratenlimits:** {state}"
|
||||
|
||||
+1
-1
@@ -400,7 +400,7 @@ gateway:
|
||||
not_git_repo: "✗ Not a git repository — cannot update."
|
||||
hermes_cmd_not_found: "✗ Could not locate the `hermes` command. Hermes is running, but the update command could not find the executable on PATH or via the current Python interpreter. Try running `hermes update` manually in your terminal."
|
||||
start_failed: "✗ Failed to start update: {error}"
|
||||
starting: "⚕ Starting Hermes update… I'll stream progress here."
|
||||
starting: "☤ Starting Hermes update… I'll stream progress here."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Rate Limits:** {state}"
|
||||
|
||||
+1
-1
@@ -384,7 +384,7 @@ gateway:
|
||||
not_git_repo: "✗ No es un repositorio git — no se puede actualizar."
|
||||
hermes_cmd_not_found: "✗ No se pudo localizar el comando `hermes`. Hermes está en ejecución, pero el comando de actualización no encontró el ejecutable en PATH ni a través del intérprete de Python actual. Intenta ejecutar `hermes update` manualmente en tu terminal."
|
||||
start_failed: "✗ No se pudo iniciar la actualización: {error}"
|
||||
starting: "⚕ Iniciando la actualización de Hermes… Transmitiré el progreso aquí."
|
||||
starting: "☤ Iniciando la actualización de Hermes… Transmitiré el progreso aquí."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Límites de tasa:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Pas un dépôt git — impossible de mettre à jour."
|
||||
hermes_cmd_not_found: "✗ Impossible de localiser la commande `hermes`. Hermes est en cours d'exécution, mais la commande de mise à jour n'a pas pu trouver l'exécutable dans le PATH ni via l'interpréteur Python actuel. Essayez d'exécuter `hermes update` manuellement dans votre terminal."
|
||||
start_failed: "✗ Échec du démarrage de la mise à jour : {error}"
|
||||
starting: "⚕ Démarrage de la mise à jour Hermes… Je diffuserai la progression ici."
|
||||
starting: "☤ Démarrage de la mise à jour Hermes… Je diffuserai la progression ici."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Limites de débit :** {state}"
|
||||
|
||||
+1
-1
@@ -391,7 +391,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Ní stór git é seo — ní féidir nuashonrú."
|
||||
hermes_cmd_not_found: "✗ Níorbh fhéidir an t-ordú `hermes` a aimsiú. Tá Hermes ag rith, ach níorbh fhéidir leis an ordú nuashonraithe an inrite a aimsiú ar PATH ná tríd an léirmhínitheoir Python reatha. Bain triail as `hermes update` a rith de láimh i do theirminéal."
|
||||
start_failed: "✗ Theip ar nuashonrú a thosú: {error}"
|
||||
starting: "⚕ Ag tosú nuashonrú Hermes… Cuirfidh mé an dul chun cinn ar shruth anseo."
|
||||
starting: "☤ Ag tosú nuashonrú Hermes… Cuirfidh mé an dul chun cinn ar shruth anseo."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Teorainneacha Ráta:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Nem git-tárhely — frissítés nem lehetséges."
|
||||
hermes_cmd_not_found: "✗ Nem sikerült megtalálni a `hermes` parancsot. A Hermes fut, de a frissítőparancs nem találta a futtatható fájlt a PATH-on vagy a jelenlegi Python interpreteren keresztül. Próbáld futtatni a `hermes update` parancsot manuálisan a terminálban."
|
||||
start_failed: "✗ Nem sikerült elindítani a frissítést: {error}"
|
||||
starting: "⚕ Hermes frissítés indítása… A folyamatot itt fogom közvetíteni."
|
||||
starting: "☤ Hermes frissítés indítása… A folyamatot itt fogom közvetíteni."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Sebességkorlátok:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Non è un repository git — impossibile aggiornare."
|
||||
hermes_cmd_not_found: "✗ Impossibile localizzare il comando `hermes`. Hermes è in esecuzione, ma il comando di aggiornamento non ha trovato l'eseguibile nel PATH o tramite l'interprete Python attuale. Prova a eseguire `hermes update` manualmente nel terminale."
|
||||
start_failed: "✗ Avvio dell'aggiornamento non riuscito: {error}"
|
||||
starting: "⚕ Avvio dell'aggiornamento di Hermes… mostrerò qui i progressi in streaming."
|
||||
starting: "☤ Avvio dell'aggiornamento di Hermes… mostrerò qui i progressi in streaming."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Limiti di frequenza:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Git リポジトリではありません — 更新できません。"
|
||||
hermes_cmd_not_found: "✗ `hermes` コマンドが見つかりません。Hermes は実行中ですが、更新コマンドは PATH 上にも現在の Python インタープリタ経由でも実行可能ファイルを見つけられませんでした。ターミナルで `hermes update` を手動で実行してみてください。"
|
||||
start_failed: "✗ 更新の開始に失敗しました: {error}"
|
||||
starting: "⚕ Hermes の更新を開始しています… 進捗をここにストリーミングします。"
|
||||
starting: "☤ Hermes の更新を開始しています… 進捗をここにストリーミングします。"
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **レート制限:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ git 저장소가 아닙니다 — 업데이트할 수 없습니다."
|
||||
hermes_cmd_not_found: "✗ `hermes` 명령을 찾을 수 없습니다. Hermes는 실행 중이지만 PATH나 현재 Python 인터프리터를 통해 실행 파일을 찾을 수 없습니다. 터미널에서 `hermes update`를 직접 실행해 보세요."
|
||||
start_failed: "✗ 업데이트 시작 실패: {error}"
|
||||
starting: "⚕ Hermes 업데이트를 시작합니다… 진행 상황을 여기에 스트리밍하겠습니다."
|
||||
starting: "☤ Hermes 업데이트를 시작합니다… 진행 상황을 여기에 스트리밍하겠습니다."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **요청 제한:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Não é um repositório git — não é possível atualizar."
|
||||
hermes_cmd_not_found: "✗ Não foi possível localizar o comando `hermes`. O Hermes está em execução, mas o comando de atualização não conseguiu encontrar o executável no PATH nem através do interpretador Python atual. Tenta executar `hermes update` manualmente no teu terminal."
|
||||
start_failed: "✗ Falha ao iniciar a atualização: {error}"
|
||||
starting: "⚕ A iniciar a atualização do Hermes… Vou transmitir o progresso aqui."
|
||||
starting: "☤ A iniciar a atualização do Hermes… Vou transmitir o progresso aqui."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Limites de taxa:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Не git-репозиторий — обновление невозможно."
|
||||
hermes_cmd_not_found: "✗ Не удалось найти команду `hermes`. Hermes запущен, но команда обновления не нашла исполняемый файл в PATH или через текущий интерпретатор Python. Попробуйте выполнить `hermes update` вручную в терминале."
|
||||
start_failed: "✗ Не удалось запустить обновление: {error}"
|
||||
starting: "⚕ Запуск обновления Hermes… Я буду транслировать прогресс сюда."
|
||||
starting: "☤ Запуск обновления Hermes… Я буду транслировать прогресс сюда."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Ограничения скорости:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Git deposu değil — güncellenemiyor."
|
||||
hermes_cmd_not_found: "✗ `hermes` komutu bulunamadı. Hermes çalışıyor, ancak güncelleme komutu yürütülebilir dosyayı PATH'te veya mevcut Python yorumlayıcısı aracılığıyla bulamadı. Terminalde `hermes update` komutunu manuel olarak çalıştırmayı deneyin."
|
||||
start_failed: "✗ Güncelleme başlatılamadı: {error}"
|
||||
starting: "⚕ Hermes güncellemesi başlatılıyor… İlerlemeyi buraya akıtacağım."
|
||||
starting: "☤ Hermes güncellemesi başlatılıyor… İlerlemeyi buraya akıtacağım."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Hız Sınırları:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ Не git-репозиторій — оновлення неможливе."
|
||||
hermes_cmd_not_found: "✗ Не вдалося знайти команду `hermes`. Hermes запущено, але команда оновлення не знайшла виконуваний файл у PATH або через поточний інтерпретатор Python. Спробуйте виконати `hermes update` вручну у вашому терміналі."
|
||||
start_failed: "✗ Не вдалося запустити оновлення: {error}"
|
||||
starting: "⚕ Запуск оновлення Hermes… Я транслюватиму прогрес сюди."
|
||||
starting: "☤ Запуск оновлення Hermes… Я транслюватиму прогрес сюди."
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **Обмеження швидкості:** {state}"
|
||||
|
||||
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ 不是 git 儲存庫 — 無法更新。"
|
||||
hermes_cmd_not_found: "✗ 找不到 `hermes` 指令。Hermes 正在執行,但更新指令無法在 PATH 上或透過目前的 Python 解譯器找到執行檔。請嘗試在終端機中手動執行 `hermes update`。"
|
||||
start_failed: "✗ 啟動更新失敗:{error}"
|
||||
starting: "⚕ 正在啟動 Hermes 更新…… 進度將在此處顯示。"
|
||||
starting: "☤ 正在啟動 Hermes 更新…… 進度將在此處顯示。"
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **速率限制:** {state}"
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ Future messages in this room will use that transcript until `/reset` or another
|
||||
not_git_repo: "✗ 不是 git 仓库 — 无法更新。"
|
||||
hermes_cmd_not_found: "✗ 无法找到 `hermes` 命令。Hermes 正在运行,但更新命令无法在 PATH 上或通过当前 Python 解释器找到可执行文件。请尝试在终端中手动运行 `hermes update`。"
|
||||
start_failed: "✗ 启动更新失败:{error}"
|
||||
starting: "⚕ 正在启动 Hermes 更新…… 进度将在此处显示。"
|
||||
starting: "☤ 正在启动 Hermes 更新…… 进度将在此处显示。"
|
||||
|
||||
usage:
|
||||
rate_limits: "⏱️ **速率限制:** {state}"
|
||||
|
||||
@@ -5367,13 +5367,13 @@ class DiscordAdapter(DiscordMediaMixin, BasePlatformAdapter):
|
||||
def _build(_channel):
|
||||
default_hint = f" (default: {default})" if default else ""
|
||||
embed = discord.Embed(
|
||||
title="⚕ Update Needs Your Input", description=f"{prompt}{default_hint}", color=discord.Color.gold(),
|
||||
title="☤ Update Needs Your Input", description=f"{prompt}{default_hint}", color=discord.Color.gold(),
|
||||
)
|
||||
view = UpdatePromptView(
|
||||
session_key=session_key, allowed_user_ids=self._allowed_user_ids,
|
||||
allowed_role_ids=self._allowed_role_ids,
|
||||
)
|
||||
content = self._self_contained_prompt_content("⚕ **Update Needs Your Input**", f"{prompt}{default_hint}")
|
||||
content = self._self_contained_prompt_content("☤ **Update Needs Your Input**", f"{prompt}{default_hint}")
|
||||
return {"content": content, "embed": embed, "view": view}, view
|
||||
result = await self._send_prompt(chat_id, metadata, _build)
|
||||
if result.success and _metadata_marks_nonconversational(metadata):
|
||||
|
||||
@@ -1706,7 +1706,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
||||
return _card_button(label, btn_type, {"hermes_update_prompt_action": answer, "update_prompt_id": prompt_id})
|
||||
|
||||
actions = [_btn("✓ Yes", "y", "primary"), _btn("✗ No", "n", "danger")]
|
||||
return _card("⚕ Update Needs Your Input", "orange", f"{prompt}{default_hint}", actions=actions)
|
||||
return _card("☤ Update Needs Your Input", "orange", f"{prompt}{default_hint}", actions=actions)
|
||||
|
||||
async def send_update_prompt(
|
||||
self, chat_id: str, prompt: str, default: str = "", session_key: str = "",
|
||||
|
||||
@@ -3781,7 +3781,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||
"""Send an inline-keyboard Yes/No prompt for the gateway ``/update`` watcher."""
|
||||
def build():
|
||||
default_hint = f" (default: {default})" if default else ""
|
||||
text = self.format_message(f"⚕ *Update needs your input:*\n\n{prompt}{default_hint}")
|
||||
text = self.format_message(f"☤ *Update needs your input:*\n\n{prompt}{default_hint}")
|
||||
keyboard = InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("✓ Yes", callback_data="update_prompt:y"),
|
||||
InlineKeyboardButton("✗ No", callback_data="update_prompt:n")]])
|
||||
@@ -4439,7 +4439,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||
if not await self._callback_authorized(query, cb, "⛔ You are not authorized to answer update prompts."):
|
||||
return
|
||||
await query.answer(text=f"Sent '{answer}' to the update process.")
|
||||
await self._edit_md_quiet(query, f"⚕ Update prompt answered: *{'Yes' if answer == 'y' else 'No'}*")
|
||||
await self._edit_md_quiet(query, f"☤ Update prompt answered: *{'Yes' if answer == 'y' else 'No'}*")
|
||||
try:
|
||||
from hermes_constants import get_hermes_home
|
||||
response_path = get_hermes_home() / ".update_response"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Regression tests for hermes_cli._ensure_utf8().
|
||||
|
||||
Covers the crash class where the setup wizard (and other banner-printing
|
||||
commands) emit box-drawing characters and the ⚕ glyph, which raise
|
||||
commands) emit box-drawing characters and the ☤ glyph, which raise
|
||||
UnicodeEncodeError when stdout/stderr are bound to a non-UTF-8 codec.
|
||||
|
||||
Historically the repair was gated on ``sys.platform == "win32"`` and only
|
||||
@@ -19,7 +19,7 @@ import hermes_cli
|
||||
|
||||
|
||||
# The exact glyphs the setup wizard / banners print (setup.py ~line 2962+).
|
||||
_BANNER = "┌─────┐\n│ ⚕ Hermes │\n└─────┘"
|
||||
_BANNER = "┌─────┐\n│ ☤ Hermes │\n└─────┘"
|
||||
|
||||
|
||||
class _FakeStream:
|
||||
@@ -109,7 +109,7 @@ def test_fallback_when_reconfigure_unavailable(monkeypatch, tmp_path):
|
||||
sys.stdout.write(_BANNER)
|
||||
sys.stdout.flush()
|
||||
fh.close()
|
||||
assert "⚕".encode("utf-8") in real_path.read_bytes()
|
||||
assert "☤".encode("utf-8") in real_path.read_bytes()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -72,14 +72,14 @@ class TestSurrogateVsAsciiSanitization:
|
||||
|
||||
def test_ascii_codec_strips_all_non_ascii(self):
|
||||
"""ASCII codec case: all non-ASCII is stripped, not replaced."""
|
||||
messages = [{"role": "user", "content": "test ⚕🤖你好 end"}]
|
||||
messages = [{"role": "user", "content": "test ☤🤖你好 end"}]
|
||||
assert _sanitize_messages_non_ascii(messages) is True
|
||||
# All non-ASCII chars removed; spaces around them collapse
|
||||
assert messages[0]["content"] == "test end"
|
||||
|
||||
def test_no_surrogates_returns_false(self):
|
||||
"""When no surrogates present, _sanitize_messages_surrogates returns False."""
|
||||
messages = [{"role": "user", "content": "hello ⚕ world"}]
|
||||
messages = [{"role": "user", "content": "hello ☤ world"}]
|
||||
assert _sanitize_messages_surrogates(messages) is False
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const padVerb = (verb: string) => `${verb}…`.padEnd(VERB_PAD_LEN, ' ')
|
||||
|
||||
// Compact alternates for the `emoji` and `ascii` indicator styles.
|
||||
// Each entry is a fixed-width (display-width) glyph.
|
||||
const EMOJI_FRAMES = ['⚕ ', '🌀', '🤔', '✨', '🍵', '🔮']
|
||||
const EMOJI_FRAMES = ['☤ ', '🌀', '🤔', '✨', '🍵', '🔮']
|
||||
const ASCII_FRAMES = ['|', '/', '-', '\\']
|
||||
|
||||
// Faster tick for spinner-style indicators — they read as motion only
|
||||
@@ -54,7 +54,7 @@ const renderIndicator = (style: IndicatorStyle, tick: number): IndicatorRender =
|
||||
|
||||
if (style === 'emoji') {
|
||||
return {
|
||||
frame: EMOJI_FRAMES[tick % EMOJI_FRAMES.length] ?? '⚕ ',
|
||||
frame: EMOJI_FRAMES[tick % EMOJI_FRAMES.length] ?? '☤ ',
|
||||
intervalMs: SPINNER_TICK_MS * 6,
|
||||
showVerb: true
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ const ComposerPane = memo(function ComposerPane({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{!composer.empty && !ui.sid && <Text color={ui.theme.color.muted}>⚕ {ui.status}</Text>}
|
||||
{!composer.empty && !ui.sid && <Text color={ui.theme.color.muted}>☤ {ui.status}</Text>}
|
||||
|
||||
<AmbientDock placement="dock-bottom" />
|
||||
<StatusRulePane at="bottom" composer={composer} status={status} />
|
||||
|
||||
+2
-2
@@ -251,10 +251,10 @@ export function themeToneHex(tone: string): string {
|
||||
|
||||
const BRAND: ThemeBrand = {
|
||||
name: 'Hermes Agent',
|
||||
icon: '⚕',
|
||||
icon: '☤',
|
||||
prompt: '❯',
|
||||
welcome: 'Type your message or /help for commands.',
|
||||
goodbye: 'Goodbye! ⚕',
|
||||
goodbye: 'Goodbye! ☤',
|
||||
tool: '┊',
|
||||
helpHeader: '(^_^)? Commands'
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ The single line at the bottom of the TUI. Segments appear only when relevant and
|
||||
| Symbol | Meaning |
|
||||
|--------|---------|
|
||||
| `⠋⠙⠹…` (braille patterns) | Busy spinner. Thinking and tool phases use different braille animation sets. |
|
||||
| `⚕ 🌀 🤔 ✨ 🍵 🔮` | Frames of the `emoji` busy-indicator style (`/indicator emoji`). The default style rotates kaomoji faces instead. |
|
||||
| `☤ 🌀 🤔 ✨ 🍵 🔮` | Frames of the `emoji` busy-indicator style (`/indicator emoji`). The default style rotates kaomoji faces instead. |
|
||||
| <code>| / - \</code> | Frames of the `ascii` busy-indicator style. |
|
||||
| `⏱` | Per-prompt elapsed time while the turn runs, e.g. `⏱ 12s/3m 45s` (turn time / session time). |
|
||||
| `⏲` | The same timer, frozen after the turn completes. |
|
||||
|
||||
@@ -137,7 +137,7 @@ The welcome banner shows your model, terminal backend, working directory, availa
|
||||
A persistent status bar sits above the input area, updating in real time:
|
||||
|
||||
```
|
||||
⚕ claude-sonnet-4-20250514 │ 12.4K/200K │ [██████░░░░] 6% │ $0.06 │ 15m
|
||||
☤ claude-sonnet-4-20250514 │ 12.4K/200K │ [██████░░░░] 6% │ $0.06 │ 15m
|
||||
```
|
||||
|
||||
| Element | Description |
|
||||
@@ -529,7 +529,7 @@ Each `/bg` prompt spawns a **completely separate agent session** in a daemon thr
|
||||
When a background task finishes, the result appears as a panel in your terminal:
|
||||
|
||||
```
|
||||
╭─ ⚕ Hermes (background #1) ──────────────────────────────────╮
|
||||
╭─ ☤ Hermes (background #1) ──────────────────────────────────╮
|
||||
│ Found 3 errors in syslog from today: │
|
||||
│ 1. OOM killer invoked at 03:22 — killed process nginx │
|
||||
│ 2. Disk I/O error on /dev/sda1 at 07:15 │
|
||||
|
||||
@@ -94,8 +94,8 @@ Text strings used throughout the CLI interface.
|
||||
|-----|-------------|---------|
|
||||
| `agent_name` | Name shown in banner title and status display | `Hermes Agent` |
|
||||
| `welcome` | Welcome message shown at CLI startup | `Welcome to Hermes Agent! Type your message or /help for commands.` |
|
||||
| `goodbye` | Message shown on exit | `Goodbye! ⚕` |
|
||||
| `response_label` | Label on the response box header | ` ⚕ Hermes ` |
|
||||
| `goodbye` | Message shown on exit | `Goodbye! ☤` |
|
||||
| `response_label` | Label on the response box header | ` ☤ Hermes ` |
|
||||
| `prompt_symbol` | Symbol before the user input prompt (bare token, renderers add a trailing space) | `❯` |
|
||||
| `help_header` | Header text for the `/help` command output | `(^_^)? Available Commands` |
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ Hermes supports voice on WhatsApp:
|
||||
|
||||
- **Incoming:** Voice messages (`.ogg` opus) are automatically transcribed using the configured STT provider: local `faster-whisper`, Groq Whisper (`GROQ_API_KEY`), or OpenAI Whisper (`VOICE_TOOLS_OPENAI_KEY`)
|
||||
- **Outgoing:** TTS responses are sent as MP3 audio file attachments
|
||||
- Agent responses are prefixed with "⚕ **Hermes Agent**" by default. You can customize or disable this in `config.yaml`:
|
||||
- Agent responses are prefixed with "☤ **Hermes Agent**" by default. You can customize or disable this in `config.yaml`:
|
||||
|
||||
```yaml
|
||||
# ~/.hermes/config.yaml
|
||||
|
||||
@@ -59,7 +59,7 @@ hermes -w -z "Fix issue #123" # 在 worktree 中以单次查询模式运行
|
||||
一个持久状态栏位于输入区域上方,实时更新:
|
||||
|
||||
```
|
||||
⚕ claude-sonnet-4-20250514 │ 12.4K/200K │ [██████░░░░] 6% │ $0.06 │ 15m
|
||||
☤ claude-sonnet-4-20250514 │ 12.4K/200K │ [██████░░░░] 6% │ $0.06 │ 15m
|
||||
```
|
||||
|
||||
| 元素 | 描述 |
|
||||
@@ -412,7 +412,7 @@ Hermes 立即确认任务并将提示符还给你:
|
||||
后台任务完成时,结果会以面板形式出现在终端中:
|
||||
|
||||
```
|
||||
╭─ ⚕ Hermes (background #1) ──────────────────────────────────╮
|
||||
╭─ ☤ Hermes (background #1) ──────────────────────────────────╮
|
||||
│ Found 3 errors in syslog from today: │
|
||||
│ 1. OOM killer invoked at 03:22 — killed process nginx │
|
||||
│ 2. Disk I/O error on /dev/sda1 at 07:15 │
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@ CLI 界面中使用的文字字符串。
|
||||
|----|------|--------|
|
||||
| `agent_name` | 横幅标题和状态显示中的名称 | `Hermes Agent` |
|
||||
| `welcome` | CLI 启动时显示的欢迎消息 | `Welcome to Hermes Agent! Type your message or /help for commands.` |
|
||||
| `goodbye` | 退出时显示的消息 | `Goodbye! ⚕` |
|
||||
| `response_label` | 响应框标题上的标签 | ` ⚕ Hermes ` |
|
||||
| `goodbye` | 退出时显示的消息 | `Goodbye! ☤` |
|
||||
| `response_label` | 响应框标题上的标签 | ` ☤ Hermes ` |
|
||||
| `prompt_symbol` | 用户输入 prompt 前的符号(裸 token,渲染器会在后面添加空格) | `❯` |
|
||||
| `help_header` | `/help` 命令输出的标题文字 | `(^_^)? Available Commands` |
|
||||
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ Hermes 支持 WhatsApp 上的语音功能:
|
||||
|
||||
- **接收:** 语音消息(`.ogg` opus 格式)会使用已配置的 STT 提供商自动转录:本地 `faster-whisper`、Groq Whisper(`GROQ_API_KEY`)或 OpenAI Whisper(`VOICE_TOOLS_OPENAI_KEY`)
|
||||
- **发送:** TTS 响应以 MP3 音频文件附件形式发送
|
||||
- Agent 响应默认以"⚕ **Hermes Agent**"为前缀。可在 `config.yaml` 中自定义或禁用:
|
||||
- Agent 响应默认以"☤ **Hermes Agent**"为前缀。可在 `config.yaml` 中自定义或禁用:
|
||||
|
||||
```yaml
|
||||
# ~/.hermes/config.yaml
|
||||
|
||||
Reference in New Issue
Block a user