Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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:]``.

Expand Down Expand Up @@ -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("-")
Expand Down
37 changes: 30 additions & 7 deletions tests/hermes_cli/test_default_interface_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
)


# ---------------------------------------------------------------------------
Expand Down
Loading