From 5f32ce048c98082e4d6a9582e7b766c52546a9e5 Mon Sep 17 00:00:00 2001 From: Omar B Date: Thu, 4 Jun 2026 11:16:41 -0700 Subject: [PATCH] fix(cli): preserve chat-local provider flags --- hermes_cli/main.py | 24 ++++++++++-- .../test_default_interface_resolution.py | 37 +++++++++++++++---- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 737353f3c84c..84772325341d 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -12530,6 +12530,7 @@ def cmd_logs(args): def _hoist_post_subcommand_global_flags( argv: list[str], known_cmds: set[str] | frozenset[str], + subcommand_option_strings: dict[str, set[str]] | None = None, ) -> list[str]: """Move known top-level launcher flags before the subcommand. @@ -12574,13 +12575,15 @@ def _hoist_post_subcommand_global_flags( prefix = head[:command_index] command_and_args = head[command_index:] + command_name = command_and_args[0] + native_options = (subcommand_option_strings or {}).get(command_name, set()) hoisted: list[str] = [] remainder: list[str] = [] i = 0 while i < len(command_and_args): token = command_and_args[i] if i > 0 and token in _POST_SUBCOMMAND_TOP_LEVEL_BOOL_FLAGS: - hoisted.append(token) + (remainder if token in native_options else hoisted).append(token) i += 1 continue @@ -12594,12 +12597,14 @@ def _hoist_post_subcommand_global_flags( None, ) if inline_value_match: - hoisted.append(token) + (remainder if inline_value_match in native_options else hoisted).append(token) i += 1 continue if token in _POST_SUBCOMMAND_TOP_LEVEL_VALUE_FLAGS and i + 1 < len(command_and_args): - hoisted.extend([token, command_and_args[i + 1]]) + (remainder if token in native_options else hoisted).extend( + [token, command_and_args[i + 1]] + ) i += 2 continue @@ -12612,6 +12617,18 @@ def _hoist_post_subcommand_global_flags( return [*prefix, *hoisted, *remainder, *tail] +def _subcommand_option_strings(subparsers) -> dict[str, set[str]]: # noqa: ANN001 + choices = getattr(subparsers, "choices", {}) or {} + return { + name: { + option + for action in getattr(parser, "_actions", []) + for option in getattr(action, "option_strings", []) + } + for name, parser in choices.items() + } + + def _first_positional_argv() -> str | None: """Return the first non-flag, non-flag-value token in ``sys.argv[1:]``. @@ -16051,6 +16068,7 @@ def cmd_acp(args): _processed_argv = _hoist_post_subcommand_global_flags( _coalesce_session_name_args(sys.argv[1:]), _known_cmds, + _subcommand_option_strings(subparsers), ) _has_cmd_token = any( t in _known_cmds for t in _processed_argv if not t.startswith("-") diff --git a/tests/hermes_cli/test_default_interface_resolution.py b/tests/hermes_cli/test_default_interface_resolution.py index 88b68076101a..3d703cf4a02a 100644 --- a/tests/hermes_cli/test_default_interface_resolution.py +++ b/tests/hermes_cli/test_default_interface_resolution.py @@ -191,13 +191,14 @@ def _dashboard_parser(self): dashboard.add_argument("--port", type=int, default=9119) dashboard.add_argument("--host", default="127.0.0.1") dashboard.add_argument("--no-open", action="store_true") - return parser, set(subparsers.choices.keys()) + return parser, subparsers def test_dashboard_accepts_trailing_tui_flag(self): - parser, known_cmds = self._dashboard_parser() + parser, subparsers = self._dashboard_parser() argv = m._hoist_post_subcommand_global_flags( ["dashboard", "--no-open", "--host", "127.0.0.1", "--port", "65535", "--tui"], - known_cmds, + set(subparsers.choices.keys()), + m._subcommand_option_strings(subparsers), ) args = parser.parse_args(argv) @@ -209,10 +210,11 @@ def test_dashboard_accepts_trailing_tui_flag(self): assert args.port == 65535 def test_dashboard_accepts_trailing_skills_and_tui_flags(self): - parser, known_cmds = self._dashboard_parser() + parser, subparsers = self._dashboard_parser() argv = m._hoist_post_subcommand_global_flags( ["dashboard", "--no-open", "--skills", "desktop-backend", "--tui"], - known_cmds, + set(subparsers.choices.keys()), + m._subcommand_option_strings(subparsers), ) args = parser.parse_args(argv) @@ -222,11 +224,32 @@ def test_dashboard_accepts_trailing_skills_and_tui_flags(self): assert args.tui is True assert args.no_open is True + def test_chat_keeps_native_provider_flag_after_subcommand(self): + parser, subparsers = self._dashboard_parser() + argv = m._hoist_post_subcommand_global_flags( + ["chat", "--provider", "gmi"], + set(subparsers.choices.keys()), + m._subcommand_option_strings(subparsers), + ) + + args = parser.parse_args(argv) + + assert argv == ["chat", "--provider", "gmi"] + assert args.command == "chat" + assert args.provider == "gmi" + def test_double_dash_stops_global_flag_hoisting(self): - _parser, known_cmds = self._dashboard_parser() + _parser, subparsers = self._dashboard_parser() argv = ["dashboard", "--", "--tui"] - assert m._hoist_post_subcommand_global_flags(argv, known_cmds) == argv + assert ( + m._hoist_post_subcommand_global_flags( + argv, + set(subparsers.choices.keys()), + m._subcommand_option_strings(subparsers), + ) + == argv + ) # ---------------------------------------------------------------------------