diff --git a/cli.py b/cli.py index eff85dbe5b..b7e41ee268 100644 --- a/cli.py +++ b/cli.py @@ -3360,22 +3360,22 @@ class HermesCLI: pass # Don't crash on import errors def _show_status(self): - """Show current status bar.""" + """Show compact startup status line.""" # Get tool count tools = get_tool_definitions(enabled_toolsets=self.enabled_toolsets, quiet_mode=True) tool_count = len(tools) if tools else 0 - + # Format model name (shorten if needed) model_short = self.model.split("/")[-1] if "/" in self.model else self.model if len(model_short) > 30: model_short = model_short[:27] + "..." - + # Get API status indicator if self.api_key: api_indicator = "[green bold]●[/]" else: api_indicator = "[red bold]●[/]" - + # Build status line with proper markup toolsets_info = "" if self.enabled_toolsets and "all" not in self.enabled_toolsets: @@ -3390,6 +3390,59 @@ class HermesCLI: f"[dim #B8860B]·[/] [bold cyan]{tool_count} tools[/]" f"{toolsets_info}{provider_info}" ) + + def _show_session_status(self): + """Show gateway-style status for the current CLI session.""" + session_meta = {} + if self._session_db: + try: + session_meta = self._session_db.get_session(self.session_id) or {} + except Exception: + session_meta = {} + + title = (session_meta.get("title") or "").strip() + + created_at = self.session_start + started_at = session_meta.get("started_at") + if started_at: + try: + created_at = datetime.fromtimestamp(float(started_at)) + except Exception: + created_at = self.session_start + + updated_at = created_at + for field in ("updated_at", "last_updated_at", "last_activity_at"): + value = session_meta.get(field) + if not value: + continue + try: + updated_at = datetime.fromtimestamp(float(value)) + break + except Exception: + pass + + agent = getattr(self, "agent", None) + total_tokens = getattr(agent, "session_total_tokens", 0) or 0 + provider = getattr(self, "provider", None) or "unknown" + model = getattr(self, "model", None) or "(unknown)" + is_running = bool(getattr(self, "_agent_running", False)) + + lines = [ + "Hermes CLI Status", + "", + f"Session ID: {self.session_id}", + f"Path: {display_hermes_home()}", + ] + if title: + lines.append(f"Title: {title}") + lines.extend([ + f"Model: {model} ({provider})", + f"Created: {created_at.strftime('%Y-%m-%d %H:%M')}", + f"Last Activity: {updated_at.strftime('%Y-%m-%d %H:%M')}", + f"Tokens: {total_tokens:,}", + f"Agent Running: {'Yes' if is_running else 'No'}", + ]) + self.console.print("\n".join(lines), highlight=False, markup=False) def _fast_command_available(self) -> bool: try: @@ -4873,6 +4926,8 @@ class HermesCLI: self._handle_skills_command(cmd_original) elif canonical == "platforms": self._show_gateway_status() + elif canonical == "status": + self._show_session_status() elif canonical == "statusbar": self._status_bar_visible = not self._status_bar_visible state = "visible" if self._status_bar_visible else "hidden" diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index d698fc0883..4fee4c3e42 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -83,8 +83,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ args_hint=""), CommandDef("queue", "Queue a prompt for the next turn (doesn't interrupt)", "Session", aliases=("q",), args_hint=""), - CommandDef("status", "Show session info", "Session", - gateway_only=True), + CommandDef("status", "Show session info", "Session"), CommandDef("profile", "Show active profile name and home directory", "Info"), CommandDef("sethome", "Set this chat as the home channel", "Session", gateway_only=True, aliases=("set-home",)), diff --git a/tests/cli/test_cli_status_command.py b/tests/cli/test_cli_status_command.py new file mode 100644 index 0000000000..bff642fdff --- /dev/null +++ b/tests/cli/test_cli_status_command.py @@ -0,0 +1,85 @@ +"""Tests for CLI /status command behavior.""" +from datetime import datetime +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +from cli import HermesCLI +from hermes_cli.commands import resolve_command + + +def _make_cli(): + cli_obj = HermesCLI.__new__(HermesCLI) + cli_obj.config = {} + cli_obj.console = MagicMock() + cli_obj.agent = None + cli_obj.conversation_history = [] + cli_obj.session_id = "session-123" + cli_obj._pending_input = MagicMock() + cli_obj._status_bar_visible = True + cli_obj.model = "openai/gpt-5.4" + cli_obj.provider = "openai" + cli_obj.session_start = datetime(2026, 4, 9, 19, 24) + cli_obj._agent_running = False + cli_obj._session_db = MagicMock() + cli_obj._session_db.get_session.return_value = None + return cli_obj + + +def test_status_command_is_available_in_cli_registry(): + cmd = resolve_command("status") + assert cmd is not None + assert cmd.gateway_only is False + + +def test_process_command_status_dispatches_without_toggling_status_bar(): + cli_obj = _make_cli() + + with patch.object(cli_obj, "_show_session_status", create=True) as mock_status: + assert cli_obj.process_command("/status") is True + + mock_status.assert_called_once_with() + assert cli_obj._status_bar_visible is True + + +def test_statusbar_still_toggles_visibility(): + cli_obj = _make_cli() + + assert cli_obj.process_command("/statusbar") is True + assert cli_obj._status_bar_visible is False + + +def test_status_prefix_prefers_status_command_over_statusbar_toggle(): + cli_obj = _make_cli() + + with patch.object(cli_obj, "_show_session_status") as mock_status: + assert cli_obj.process_command("/sta") is True + + mock_status.assert_called_once_with() + assert cli_obj._status_bar_visible is True + + +def test_show_session_status_prints_gateway_style_summary(): + cli_obj = _make_cli() + cli_obj.agent = SimpleNamespace( + session_total_tokens=321, + session_api_calls=4, + ) + cli_obj._session_db.get_session.return_value = { + "title": "My titled session", + "started_at": 1775791440, + } + + with patch("cli.display_hermes_home", return_value="~/.hermes"): + cli_obj._show_session_status() + + printed = "\n".join(str(call.args[0]) for call in cli_obj.console.print.call_args_list) + assert "Hermes CLI Status" in printed + assert "Session ID: session-123" in printed + assert "Path: ~/.hermes" in printed + assert "Title: My titled session" in printed + assert "Model: openai/gpt-5.4 (openai)" in printed + assert "Tokens: 321" in printed + assert "Agent Running: No" in printed + _, kwargs = cli_obj.console.print.call_args + assert kwargs.get("highlight") is False + assert kwargs.get("markup") is False