diff --git a/cli.py b/cli.py index d05999b73c5..e20472c7623 100644 --- a/cli.py +++ b/cli.py @@ -2819,7 +2819,7 @@ class HermesCLI: api_key: str = None, base_url: str = None, max_turns: int = None, - verbose: bool = False, + verbose: Optional[bool] = None, compact: bool = False, resume: str = None, checkpoints: bool = False, @@ -14439,7 +14439,7 @@ def main( api_key: str = None, base_url: str = None, max_turns: int = None, - verbose: bool = False, + verbose: Optional[bool] = None, quiet: bool = False, compact: bool = False, list_tools: bool = False, diff --git a/hermes_cli/_parser.py b/hermes_cli/_parser.py index eb968628673..cf4ffc34e5c 100644 --- a/hermes_cli/_parser.py +++ b/hermes_cli/_parser.py @@ -269,7 +269,11 @@ def build_top_level_parser(): help="Inference provider (default: auto). Built-in or a user-defined name from `providers:` in config.yaml.", ) chat_parser.add_argument( - "-v", "--verbose", action="store_true", help="Verbose output" + "-v", + "--verbose", + action="store_true", + default=argparse.SUPPRESS, + help="Verbose output", ) chat_parser.add_argument( "-Q", diff --git a/hermes_cli/main.py b/hermes_cli/main.py index bf75a51891f..7477ebfa2ac 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -1454,7 +1454,7 @@ def _launch_tui( provider: Optional[str] = None, toolsets: object = None, skills: object = None, - verbose: bool = False, + verbose: Optional[bool] = None, quiet: bool = False, query: Optional[str] = None, image: Optional[str] = None, @@ -1763,7 +1763,7 @@ def cmd_chat(args): provider=getattr(args, "provider", None), toolsets=getattr(args, "toolsets", None), skills=getattr(args, "skills", None), - verbose=getattr(args, "verbose", False), + verbose=getattr(args, "verbose", None), quiet=getattr(args, "quiet", False), query=getattr(args, "query", None), image=getattr(args, "image", None), @@ -1783,7 +1783,7 @@ def cmd_chat(args): "provider": getattr(args, "provider", None), "toolsets": args.toolsets, "skills": getattr(args, "skills", None), - "verbose": args.verbose, + "verbose": getattr(args, "verbose", None), "quiet": getattr(args, "quiet", False), "query": args.query, "image": getattr(args, "image", None), @@ -13811,7 +13811,7 @@ Examples: ("model", None), ("provider", None), ("toolsets", None), - ("verbose", False), + ("verbose", None), ("worktree", False), ]: if not hasattr(args, attr): @@ -13826,7 +13826,7 @@ Examples: ("model", None), ("provider", None), ("toolsets", None), - ("verbose", False), + ("verbose", None), ("resume", None), ("continue_last", None), ("worktree", False), diff --git a/tests/cli/test_tool_progress_scrollback.py b/tests/cli/test_tool_progress_scrollback.py index 7924f41598b..a8e807da2ec 100644 --- a/tests/cli/test_tool_progress_scrollback.py +++ b/tests/cli/test_tool_progress_scrollback.py @@ -14,9 +14,10 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # Module-level reference to the cli module (set by _make_cli on first call) _cli_mod = None +_UNSET = object() -def _make_cli(tool_progress="all"): +def _make_cli(tool_progress="all", verbose=_UNSET): """Create a HermesCLI instance with minimal mocking.""" global _cli_mod _clean_config = { @@ -54,7 +55,9 @@ def _make_cli(tool_progress="all"): _cli_mod = mod with patch.object(mod, "get_tool_definitions", return_value=[]), \ patch.dict(mod.__dict__, {"CLI_CONFIG": _clean_config}): - return mod.HermesCLI() + if verbose is _UNSET: + return mod.HermesCLI() + return mod.HermesCLI(verbose=verbose) class TestToolProgressScrollback: @@ -168,6 +171,20 @@ class TestToolProgressScrollback: mock_print.assert_not_called() + def test_verbose_mode_config_enables_cli_verbose_by_default(self): + """Config-only display.tool_progress=verbose should enable verbose output.""" + cli = _make_cli(tool_progress="verbose") + + assert cli.tool_progress_mode == "verbose" + assert cli.verbose is True + + def test_explicit_non_verbose_argument_still_overrides_verbose_config(self): + """An explicit non-verbose value should keep overriding the config fallback.""" + cli = _make_cli(tool_progress="verbose", verbose=False) + + assert cli.tool_progress_mode == "verbose" + assert cli.verbose is False + def test_pending_info_stores_on_started(self): """tool.started stores args for later use by tool.completed.""" cli = _make_cli(tool_progress="all") diff --git a/tests/hermes_cli/test_argparse_flag_propagation.py b/tests/hermes_cli/test_argparse_flag_propagation.py index 741425a82dc..c3d8e80db32 100644 --- a/tests/hermes_cli/test_argparse_flag_propagation.py +++ b/tests/hermes_cli/test_argparse_flag_propagation.py @@ -57,6 +57,59 @@ def _build_parser(): return parser +class TestChatVerboseArg: + """Verify chat --verbose preserves config fallback when absent.""" + + def test_chat_without_verbose_leaves_attribute_unset(self): + from hermes_cli._parser import build_top_level_parser + + parser, _subparsers, _chat_parser = build_top_level_parser() + args = parser.parse_args(["chat"]) + + assert not hasattr(args, "verbose") + + def test_chat_verbose_sets_attribute_true(self): + from hermes_cli._parser import build_top_level_parser + + parser, _subparsers, _chat_parser = build_top_level_parser() + args = parser.parse_args(["chat", "--verbose"]) + + assert args.verbose is True + + def test_cmd_chat_forwards_none_when_verbose_is_absent(self, monkeypatch): + import types + import sys + + import hermes_cli.main as main_mod + from hermes_cli._parser import build_top_level_parser + + parser, _subparsers, chat_parser = build_top_level_parser() + chat_parser.set_defaults(func=main_mod.cmd_chat) + args = parser.parse_args(["chat"]) + captured = {} + fake_cli = types.ModuleType("cli") + + def fake_main(**kwargs): + captured.update(kwargs) + + setattr(fake_cli, "main", fake_main) + fake_banner = types.ModuleType("hermes_cli.banner") + setattr(fake_banner, "prefetch_update_check", lambda: None) + fake_skills_sync = types.ModuleType("tools.skills_sync") + setattr(fake_skills_sync, "sync_skills", lambda quiet=True: None) + + monkeypatch.setitem(sys.modules, "cli", fake_cli) + monkeypatch.setitem(sys.modules, "hermes_cli.banner", fake_banner) + monkeypatch.setitem(sys.modules, "tools.skills_sync", fake_skills_sync) + monkeypatch.setattr(main_mod, "_has_any_provider_configured", lambda: True) + monkeypatch.setattr(main_mod, "_pin_kanban_board_env", lambda: None) + + main_mod.cmd_chat(args) + + assert captured["quiet"] is False + assert "verbose" not in captured + + class TestYoloEnvVar: """Verify --yolo sets HERMES_YOLO_MODE regardless of flag position.