refactor(cli): split main() into _build_cli_parser/_parse_cli_args/_default_to_chat helpers

main() is now a ~110-line orchestrator: startup prologue, parser build,
container routing, bpo-9338-safe parse, --version/--yolo/--oneshot, chat
default, dispatch. The two identical plugin add_parser blocks collapse into
_attach_plugin_cli_command; the two default-to-chat attr loops merge. Parser
tree, --help output and set_defaults are unchanged (399 parsers byte-diffed).
This commit is contained in:
Teknium
2026-09-02 11:39:56 -07:00
parent b81ab901f3
commit 060039d169
+248 -491
View File
@@ -13453,6 +13453,250 @@ def _advertise_agent_env() -> None:
os.environ.setdefault("HERMES_AGENT", "true")
def _attach_plugin_cli_command(subparsers, cmd_info) -> None:
"""Register one plugin-provided top-level command from its descriptor."""
plugin_parser = subparsers.add_parser(
cmd_info["name"],
help=cmd_info["help"],
description=cmd_info.get("description", ""),
formatter_class=__import__("argparse").RawDescriptionHelpFormatter,
)
cmd_info["setup_fn"](plugin_parser)
if cmd_info.get("handler_fn") is not None:
plugin_parser.set_defaults(func=cmd_info["handler_fn"])
def _register_plugin_cli_commands(subparsers) -> None:
"""Plugin CLI commands — dynamically registered by memory/general plugins.
Plugins provide a register_cli(subparser) function that builds their own
argparse tree; no hardcoded plugin commands in main.py. Skipped when the
invocation already targets a known built-in subcommand (``hermes --help``,
``hermes logs``, ...) — eagerly importing every bundled plugin module
(google.cloud.pubsub_v1, aiohttp, grpc, PIL …) costs 500-650ms.
"""
if not _plugin_cli_discovery_needed():
return
try:
from plugins.memory import discover_plugin_cli_commands
from hermes_cli.plugins import discover_plugins, get_plugin_manager
seen_plugin_commands = set()
for cmd_info in discover_plugin_cli_commands():
_attach_plugin_cli_command(subparsers, cmd_info)
seen_plugin_commands.add(cmd_info["name"])
discover_plugins()
# A bundled platform whose top-level CLI command is the one being
# invoked is still only a deferred entry at this point; import it
# so its register_cli_command side effect runs before we read
# _cli_commands (issue #54678).
_resolve_deferred_platform_cli_command(_first_positional_argv())
for cmd_info in get_plugin_manager()._cli_commands.values():
if cmd_info["name"] not in seen_plugin_commands:
_attach_plugin_cli_command(subparsers, cmd_info)
except Exception as _exc:
logging.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
def _build_cli_parser():
"""Build the full ``hermes`` argparse tree. Returns ``(parser, subparsers)``.
Registration ORDER is the order subcommands appear in ``hermes --help``;
keep it stable. Each group lives in ``hermes_cli/subcommands/<group>.py``
(handlers injected so those modules never import main) or exposes its own
``register_cli``/``build_parser`` from a sibling module.
"""
from hermes_cli._parser import build_top_level_parser
parser, subparsers, chat_parser = build_top_level_parser()
chat_parser.set_defaults(func=cmd_chat)
build_model_parser(subparsers, cmd_model=cmd_model)
build_moa_parser(subparsers)
build_fallback_parser(subparsers)
build_worktree_parser(subparsers)
build_browser_parser(subparsers)
build_secrets_parser(subparsers)
# OUTBOUND iron-proxy egress firewall; ``hermes proxy`` (gateway group) is
# the separate INBOUND OAuth-aggregator reverse proxy.
build_egress_parser(subparsers)
build_migrate_parser(subparsers)
build_gateway_parser(
subparsers, cmd_gateway=cmd_gateway, cmd_proxy=cmd_proxy, cmd_gateway_enroll=cmd_gateway_enroll
)
# LSP is optional infrastructure — never let a registration failure
# break the CLI overall.
try:
from agent.lsp.cli import register_subparser as _lsp_register
_lsp_register(subparsers)
except Exception as _lsp_err: # noqa: BLE001
logger.debug("LSP CLI registration failed: %s", _lsp_err)
build_setup_parser(subparsers, cmd_setup=cmd_setup)
build_whatsapp_parser(subparsers, cmd_whatsapp=cmd_whatsapp)
build_whatsapp_cloud_parser(subparsers, cmd_whatsapp_cloud=cmd_whatsapp_cloud)
build_slack_parser(subparsers, cmd_slack=cmd_slack)
# send command — pipe shell-script output to any configured platform
from hermes_cli.send_cmd import register_send_subparser
register_send_subparser(subparsers)
build_login_parser(subparsers, cmd_login=cmd_login)
build_logout_parser(subparsers, cmd_logout=cmd_logout)
build_auth_parser(subparsers, cmd_auth=cmd_auth)
build_status_parser(subparsers, cmd_status=cmd_status)
build_pause_parser(subparsers)
build_cron_parser(subparsers, cmd_cron=cmd_cron)
build_sync_parser(subparsers, cmd_sync=cmd_sync)
build_webhook_parser(subparsers, cmd_webhook=cmd_webhook)
# peer command — bot-to-bot DMs across machines (peer Hermes gateways)
from hermes_cli.subcommands.peer import build_peer_parser
build_peer_parser(subparsers)
# portal command — Nous Portal status + Tool Gateway routing
from hermes_cli.portal_cli import add_parser as _add_portal_parser
_add_portal_parser(subparsers)
# kanban command — multi-profile collaboration board
from hermes_cli.kanban import build_parser as _build_kanban_parser
_build_kanban_parser(subparsers).set_defaults(func=cmd_kanban)
# project command — named, multi-folder workspaces
from hermes_cli.projects_cmd import build_parser as _build_project_parser
_build_project_parser(subparsers).set_defaults(func=cmd_project)
build_hooks_parser(subparsers, cmd_hooks=cmd_hooks)
build_doctor_parser(subparsers, cmd_doctor=cmd_doctor)
build_verify_parser(subparsers, cmd_verify=cmd_verify)
build_security_parser(subparsers, cmd_security=cmd_security)
build_approvals_parser(subparsers, cmd_approvals=cmd_approvals)
build_dump_parser(subparsers, cmd_dump=cmd_dump)
build_debug_parser(subparsers, cmd_debug=cmd_debug)
build_backup_parser(subparsers, cmd_backup=cmd_backup)
build_checkpoints_parser(subparsers)
build_import_cmd_parser(subparsers, cmd_import=cmd_import)
build_import_agent_parser(subparsers, cmd_import_agent=cmd_import_agent)
build_config_parser(subparsers, cmd_config=cmd_config)
build_skin_parser(subparsers, cmd_skin=cmd_skin)
build_console_parser(subparsers, cmd_console=cmd_console)
build_pairing_parser(subparsers, cmd_pairing=cmd_pairing)
build_skills_parser(subparsers, cmd_skills=cmd_skills)
build_bundles_parser(subparsers)
build_plugins_parser(subparsers, cmd_plugins=cmd_plugins)
_register_plugin_cli_commands(subparsers)
build_curator_parser(subparsers)
build_pets_parser(subparsers)
build_journey_parser(subparsers)
build_memory_parser(subparsers, cmd_memory=cmd_memory)
build_tools_parser(subparsers, cmd_tools=cmd_tools)
build_computer_use_parser(subparsers)
build_mcp_parser(subparsers, cmd_mcp=cmd_mcp)
# Lazy indirection: sessions_cmd is only imported when the subcommand
# runs, and monkeypatches on hermes_cli.main.cmd_sessions keep working.
build_sessions_parser(subparsers, cmd_sessions=lambda a, **kw: _self().cmd_sessions(a, **kw))
build_insights_parser(subparsers, cmd_insights=cmd_insights)
build_monitoring_parser(subparsers, cmd_monitoring=cmd_monitoring)
build_claw_parser(subparsers, cmd_claw=cmd_claw)
# NOTE: the `hermes version` subcommand was removed — `hermes --version`
# / `-V` now carries the full output including update status.
build_update_parser(subparsers, cmd_update=cmd_update)
build_uninstall_parser(subparsers, cmd_uninstall=cmd_uninstall)
build_acp_parser(subparsers, cmd_acp=cmd_acp)
build_profile_parser(subparsers, cmd_profile=cmd_profile)
build_completion_parser(subparsers, cmd_completion=cmd_completion, parser=parser)
build_dashboard_parser(
subparsers,
cmd_dashboard=cmd_dashboard,
cmd_dashboard_register=cmd_dashboard_register,
)
# desktop (a.k.a. gui): the canonical name is "desktop"; "gui" is kept as
# a deprecated alias for one release. The Hermes-Setup.exe success screen
# tells users to run `hermes desktop`, so that name must be the one shown
# in --help (argparse promotes the primary name; aliases stay hidden).
build_gui_parser(subparsers, cmd_gui=cmd_gui)
build_logs_parser(subparsers, cmd_logs=cmd_logs)
build_prompt_size_parser(subparsers, cmd_prompt_size=cmd_prompt_size)
return parser, subparsers
def _parse_cli_args(parser, subparsers, argv):
"""Parse ``argv`` with the bpo-9338 subparser-routing workaround.
argv is first pre-processed so unquoted multi-word session names after
-c / -r merge into one token (``hermes -c Pokemon Agent Dev`` →
``hermes -c 'Pokemon Agent Dev'``).
On some Python versions (notably <3.11), argparse fails to route
subcommand tokens when the parent parser has nargs='?' optional arguments
(--continue): "unrecognized arguments: model" even though 'model' is a
registered subcommand. Fix: when argv contains a token matching a known
subcommand, set subparsers.required=True to force deterministic routing.
If that fails (e.g. 'hermes -c model' where 'model' is consumed as the
session name for --continue), fall back to the default behaviour.
"""
import io as _io
_processed_argv = _coalesce_session_name_args(argv)
_known_cmds = (
set(subparsers.choices.keys()) if hasattr(subparsers, "choices") else set()
)
_has_cmd_token = any(
t in _known_cmds for t in _processed_argv if not t.startswith("-")
)
if not _has_cmd_token:
subparsers.required = False
return parser.parse_args(_processed_argv)
subparsers.required = True
_saved_stderr = sys.stderr
try:
sys.stderr = _io.StringIO()
args = parser.parse_args(_processed_argv)
sys.stderr = _saved_stderr
except SystemExit as exc:
sys.stderr = _saved_stderr
# Help/version flags (exit code 0) already printed output —
# re-raise immediately to avoid a second parse_args printing
# the same help text again (#10230).
if exc.code == 0:
raise
# Subcommand name was consumed as a flag value (e.g. -c model).
# Fall back to optional subparsers so argparse handles it normally.
subparsers.required = False
args = parser.parse_args(_processed_argv)
return args
def _default_to_chat(args) -> None:
"""No subcommand given: run chat, filling the chat-parser attrs it expects."""
# Top-level --resume / --continue is a shortcut to chat.
if args.resume or args.continue_last:
args.command = "chat"
for attr, default in [
("query", None),
("model", None),
("provider", None),
("toolsets", None),
("verbose", None),
("resume", None),
("continue_last", None),
("worktree", False),
]:
if not hasattr(args, attr):
setattr(args, attr, default)
cmd_chat(args)
def cmd_import_agent(args):
from hermes_cli.agent_import import import_agent_command
import_agent_command(args)
def main():
"""Main entry point for hermes CLI."""
# Cosmetic: make the process show up as 'hermes' instead of 'python3.11'
@@ -13518,425 +13762,8 @@ def main():
if _try_fast_chat_launch():
return
from hermes_cli._parser import build_top_level_parser
parser, subparsers = _build_cli_parser()
parser, subparsers, chat_parser = build_top_level_parser()
chat_parser.set_defaults(func=cmd_chat)
# =========================================================================
# model command (parser built in hermes_cli/subcommands/model.py)
# =========================================================================
build_model_parser(subparsers, cmd_model=cmd_model)
build_moa_parser(subparsers)
# =========================================================================
# fallback command — manage the fallback provider chain
# =========================================================================
build_fallback_parser(subparsers)
# =========================================================================
# worktree command — audit/reclaim accumulated git worktrees + branches
# =========================================================================
build_worktree_parser(subparsers)
# =========================================================================
# browser command — real-profile helpers (agent-invoked, user-approved)
# =========================================================================
build_browser_parser(subparsers)
# =========================================================================
# secrets command — external secret managers (Bitwarden, 1Password)
# =========================================================================
build_secrets_parser(subparsers)
# =========================================================================
# egress command — iron-proxy outbound credential-injection firewall
# =========================================================================
build_egress_parser(subparsers)
# =========================================================================
# migrate command
# =========================================================================
build_migrate_parser(subparsers)
# =========================================================================
# gateway + proxy commands (parsers built in hermes_cli/subcommands/gateway.py)
# =========================================================================
build_gateway_parser(
subparsers, cmd_gateway=cmd_gateway, cmd_proxy=cmd_proxy, cmd_gateway_enroll=cmd_gateway_enroll
)
# =========================================================================
# lsp command
# =========================================================================
try:
from agent.lsp.cli import register_subparser as _lsp_register
_lsp_register(subparsers)
except Exception as _lsp_err: # noqa: BLE001
# LSP is optional infrastructure — never let a registration
# failure break the CLI overall.
logger.debug("LSP CLI registration failed: %s", _lsp_err)
# =========================================================================
# setup command (parser built in hermes_cli/subcommands/setup.py)
# =========================================================================
build_setup_parser(subparsers, cmd_setup=cmd_setup)
# =========================================================================
# whatsapp command (parser built in hermes_cli/subcommands/whatsapp.py)
# =========================================================================
build_whatsapp_parser(subparsers, cmd_whatsapp=cmd_whatsapp)
# =========================================================================
# whatsapp-cloud command (official Meta Cloud API; complement to Baileys)
# =========================================================================
build_whatsapp_cloud_parser(subparsers, cmd_whatsapp_cloud=cmd_whatsapp_cloud)
# =========================================================================
# slack command (parser built in hermes_cli/subcommands/slack.py)
# =========================================================================
build_slack_parser(subparsers, cmd_slack=cmd_slack)
# =========================================================================
# send command — pipe shell-script output to any configured platform
# =========================================================================
from hermes_cli.send_cmd import register_send_subparser
register_send_subparser(subparsers)
# =========================================================================
# login command (parser built in hermes_cli/subcommands/login.py)
# =========================================================================
build_login_parser(subparsers, cmd_login=cmd_login)
# =========================================================================
# logout command (parser built in hermes_cli/subcommands/logout.py)
# =========================================================================
build_logout_parser(subparsers, cmd_logout=cmd_logout)
# =========================================================================
# auth command (parser built in hermes_cli/subcommands/auth.py)
# =========================================================================
build_auth_parser(subparsers, cmd_auth=cmd_auth)
# =========================================================================
# status command (parser built in hermes_cli/subcommands/status.py)
# =========================================================================
build_status_parser(subparsers, cmd_status=cmd_status)
# =========================================================================
# pause / resume commands (parser built in hermes_cli/subcommands/pause.py)
# =========================================================================
build_pause_parser(subparsers)
# =========================================================================
# cron command (parser built in hermes_cli/subcommands/cron.py)
# =========================================================================
build_cron_parser(subparsers, cmd_cron=cmd_cron)
build_sync_parser(subparsers, cmd_sync=cmd_sync)
# =========================================================================
# webhook command (parser built in hermes_cli/subcommands/webhook.py)
# =========================================================================
build_webhook_parser(subparsers, cmd_webhook=cmd_webhook)
# =========================================================================
# peer command — bot-to-bot DMs across machines (peer Hermes gateways)
# =========================================================================
from hermes_cli.subcommands.peer import build_peer_parser
build_peer_parser(subparsers)
# =========================================================================
# portal command — Nous Portal status + Tool Gateway routing
# =========================================================================
from hermes_cli.portal_cli import add_parser as _add_portal_parser
_add_portal_parser(subparsers)
# =========================================================================
# kanban command — multi-profile collaboration board
# =========================================================================
from hermes_cli.kanban import build_parser as _build_kanban_parser
kanban_parser = _build_kanban_parser(subparsers)
kanban_parser.set_defaults(func=cmd_kanban)
# =========================================================================
# project command — named, multi-folder workspaces
# =========================================================================
from hermes_cli.projects_cmd import build_parser as _build_project_parser
project_parser = _build_project_parser(subparsers)
project_parser.set_defaults(func=cmd_project)
# =========================================================================
# hooks command — shell-hook inspection and management
# =========================================================================
# hooks command (parser built in hermes_cli/subcommands/hooks.py)
# =========================================================================
build_hooks_parser(subparsers, cmd_hooks=cmd_hooks)
# =========================================================================
# doctor command (parser built in hermes_cli/subcommands/doctor.py)
# =========================================================================
build_doctor_parser(subparsers, cmd_doctor=cmd_doctor)
# =========================================================================
# verify command (parser built in hermes_cli/subcommands/verify.py)
# =========================================================================
build_verify_parser(subparsers, cmd_verify=cmd_verify)
# =========================================================================
# security command — on-demand supply-chain audit
# =========================================================================
# security command (parser built in hermes_cli/subcommands/security.py)
# =========================================================================
build_security_parser(subparsers, cmd_security=cmd_security)
# =========================================================================
# approvals command (parser built in hermes_cli/subcommands/approvals.py)
# =========================================================================
build_approvals_parser(subparsers, cmd_approvals=cmd_approvals)
# =========================================================================
# dump command (parser built in hermes_cli/subcommands/dump.py)
# =========================================================================
build_dump_parser(subparsers, cmd_dump=cmd_dump)
# =========================================================================
# debug command (parser built in hermes_cli/subcommands/debug.py)
# =========================================================================
build_debug_parser(subparsers, cmd_debug=cmd_debug)
# =========================================================================
# backup command (parser built in hermes_cli/subcommands/backup.py)
# =========================================================================
build_backup_parser(subparsers, cmd_backup=cmd_backup)
# =========================================================================
# checkpoints command
# =========================================================================
build_checkpoints_parser(subparsers)
# =========================================================================
# import command (parser built in hermes_cli/subcommands/import_cmd.py)
# =========================================================================
build_import_cmd_parser(subparsers, cmd_import=cmd_import)
# =========================================================================
# import-agent command (parser: hermes_cli/subcommands/import_agent.py)
# =========================================================================
def cmd_import_agent(args):
from hermes_cli.agent_import import import_agent_command
import_agent_command(args)
build_import_agent_parser(subparsers, cmd_import_agent=cmd_import_agent)
# =========================================================================
# config command (parser built in hermes_cli/subcommands/config.py)
# =========================================================================
build_config_parser(subparsers, cmd_config=cmd_config)
# =========================================================================
# skin command (parser built in hermes_cli/subcommands/skin.py)
# =========================================================================
build_skin_parser(subparsers, cmd_skin=cmd_skin)
# =========================================================================
# console command (parser built in hermes_cli/subcommands/console.py)
# =========================================================================
build_console_parser(subparsers, cmd_console=cmd_console)
# =========================================================================
# pairing command (parser built in hermes_cli/subcommands/pairing.py)
# =========================================================================
build_pairing_parser(subparsers, cmd_pairing=cmd_pairing)
# =========================================================================
# skills command (parser built in hermes_cli/subcommands/skills.py)
# =========================================================================
build_skills_parser(subparsers, cmd_skills=cmd_skills)
# =========================================================================
# bundles command — skill bundles (alias /<name> for multiple skills)
# =========================================================================
build_bundles_parser(subparsers)
# =========================================================================
# plugins command (parser built in hermes_cli/subcommands/plugins.py)
# =========================================================================
build_plugins_parser(subparsers, cmd_plugins=cmd_plugins)
# =========================================================================
# Plugin CLI commands — dynamically registered by memory/general plugins.
# Plugins provide a register_cli(subparser) function that builds their
# own argparse tree. No hardcoded plugin commands in main.py.
#
# Skipped when the invocation is already targeting a known built-in
# subcommand — ``hermes --help``, ``hermes logs``,
# etc. This avoids eagerly importing every bundled plugin module
# (google.cloud.pubsub_v1, aiohttp, grpc, PIL …) which costs
# 500-650ms on typical installs.
# =========================================================================
if _plugin_cli_discovery_needed():
try:
from plugins.memory import discover_plugin_cli_commands
from hermes_cli.plugins import discover_plugins, get_plugin_manager
seen_plugin_commands = set()
for cmd_info in discover_plugin_cli_commands():
plugin_parser = subparsers.add_parser(
cmd_info["name"],
help=cmd_info["help"],
description=cmd_info.get("description", ""),
formatter_class=__import__("argparse").RawDescriptionHelpFormatter,
)
cmd_info["setup_fn"](plugin_parser)
if cmd_info.get("handler_fn") is not None:
plugin_parser.set_defaults(func=cmd_info["handler_fn"])
seen_plugin_commands.add(cmd_info["name"])
discover_plugins()
# A bundled platform whose top-level CLI command is the one being
# invoked is still only a deferred entry at this point; import it
# so its register_cli_command side effect runs before we read
# _cli_commands (issue #54678).
_resolve_deferred_platform_cli_command(_first_positional_argv())
for cmd_info in get_plugin_manager()._cli_commands.values():
if cmd_info["name"] in seen_plugin_commands:
continue
plugin_parser = subparsers.add_parser(
cmd_info["name"],
help=cmd_info["help"],
description=cmd_info.get("description", ""),
formatter_class=__import__("argparse").RawDescriptionHelpFormatter,
)
cmd_info["setup_fn"](plugin_parser)
if cmd_info.get("handler_fn") is not None:
plugin_parser.set_defaults(func=cmd_info["handler_fn"])
except Exception as _exc:
logging.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
# =========================================================================
# curator command — background skill maintenance
# =========================================================================
build_curator_parser(subparsers)
# =========================================================================
# pets command — petdex animated mascots (CLI / TUI / desktop display)
# =========================================================================
build_pets_parser(subparsers)
# =========================================================================
# journey command — learned skills + memories over time, in the terminal
# =========================================================================
build_journey_parser(subparsers)
# =========================================================================
# memory command (parser built in hermes_cli/subcommands/memory.py)
# =========================================================================
build_memory_parser(subparsers, cmd_memory=cmd_memory)
# =========================================================================
# tools command (parser built in hermes_cli/subcommands/tools.py)
# =========================================================================
build_tools_parser(subparsers, cmd_tools=cmd_tools)
# =========================================================================
# computer-use command — manage Computer Use (cua-driver)
# =========================================================================
build_computer_use_parser(subparsers)
# =========================================================================
# mcp command (parser built in hermes_cli/subcommands/mcp.py)
# =========================================================================
build_mcp_parser(subparsers, cmd_mcp=cmd_mcp)
# =========================================================================
# sessions command
# =========================================================================
build_sessions_parser(subparsers, cmd_sessions=lambda a, **kw: _self().cmd_sessions(a, **kw))
# =========================================================================
# insights command (parser built in hermes_cli/subcommands/insights.py)
# =========================================================================
build_insights_parser(subparsers, cmd_insights=cmd_insights)
build_monitoring_parser(subparsers, cmd_monitoring=cmd_monitoring)
# =========================================================================
# claw command (parser built in hermes_cli/subcommands/claw.py)
# =========================================================================
build_claw_parser(subparsers, cmd_claw=cmd_claw)
# NOTE: the `hermes version` subcommand was removed — `hermes --version`
# / `-V` now carries the full output including update status.
# =========================================================================
# update command (parser built in hermes_cli/subcommands/update.py)
# =========================================================================
build_update_parser(subparsers, cmd_update=cmd_update)
# =========================================================================
# uninstall command (parser built in hermes_cli/subcommands/uninstall.py)
# =========================================================================
build_uninstall_parser(subparsers, cmd_uninstall=cmd_uninstall)
# =========================================================================
# acp command (parser built in hermes_cli/subcommands/acp.py)
# =========================================================================
build_acp_parser(subparsers, cmd_acp=cmd_acp)
# =========================================================================
# profile command (parser built in hermes_cli/subcommands/profile.py)
# =========================================================================
build_profile_parser(subparsers, cmd_profile=cmd_profile)
# =========================================================================
# completion command
# =========================================================================
build_completion_parser(subparsers, cmd_completion=cmd_completion, parser=parser)
# =========================================================================
# dashboard command (parser built in hermes_cli/subcommands/dashboard.py)
# =========================================================================
build_dashboard_parser(
subparsers,
cmd_dashboard=cmd_dashboard,
cmd_dashboard_register=cmd_dashboard_register,
)
# =========================================================================
# desktop (a.k.a. gui) command
#
# The canonical name is "desktop"; "gui" is kept as a deprecated alias
# for one release. The Hermes-Setup.exe success screen tells users to
# run `hermes desktop` from a terminal, so the canonical name needs
# to be the one that appears in --help (argparse promotes the primary
# name; aliases stay hidden).
# =========================================================================
# gui command (parser built in hermes_cli/subcommands/gui.py)
# =========================================================================
build_gui_parser(subparsers, cmd_gui=cmd_gui)
# =========================================================================
# logs command (parser built in hermes_cli/subcommands/logs.py)
# =========================================================================
build_logs_parser(subparsers, cmd_logs=cmd_logs)
# =========================================================================
# prompt-size command (parser built in hermes_cli/subcommands/prompt_size.py)
# =========================================================================
build_prompt_size_parser(subparsers, cmd_prompt_size=cmd_prompt_size)
# =========================================================================
# Parse and execute
# =========================================================================
# Pre-process argv so unquoted multi-word session names after -c / -r
# are merged into a single token before argparse sees them.
# e.g. ``hermes -c Pokemon Agent Dev`` → ``hermes -c 'Pokemon Agent Dev'``
# ── Container-aware routing ────────────────────────────────────────
# When NixOS container mode is active, route ALL subcommands into
# the managed container. This MUST run before parse_args() so that
@@ -13951,50 +13778,8 @@ def main():
# and raises OSError on failure (which propagates as a traceback).
sys.exit(1)
_processed_argv = _coalesce_session_name_args(sys.argv[1:])
args = _parse_cli_args(parser, subparsers, sys.argv[1:])
# ── Defensive subparser routing (bpo-9338 workaround) ───────────
# On some Python versions (notably <3.11), argparse fails to route
# subcommand tokens when the parent parser has nargs='?' optional
# arguments (--continue). The symptom: "unrecognized arguments: model"
# even though 'model' is a registered subcommand.
#
# Fix: when argv contains a token matching a known subcommand, set
# subparsers.required=True to force deterministic routing. If that
# fails (e.g. 'hermes -c model' where 'model' is consumed as the
# session name for --continue), fall back to the default behaviour.
import io as _io
_known_cmds = (
set(subparsers.choices.keys()) if hasattr(subparsers, "choices") else set()
)
_has_cmd_token = any(
t in _known_cmds for t in _processed_argv if not t.startswith("-")
)
if _has_cmd_token:
subparsers.required = True
_saved_stderr = sys.stderr
try:
sys.stderr = _io.StringIO()
args = parser.parse_args(_processed_argv)
sys.stderr = _saved_stderr
except SystemExit as exc:
sys.stderr = _saved_stderr
# Help/version flags (exit code 0) already printed output —
# re-raise immediately to avoid a second parse_args printing
# the same help text again (#10230).
if exc.code == 0:
raise
# Subcommand name was consumed as a flag value (e.g. -c model).
# Fall back to optional subparsers so argparse handles it normally.
subparsers.required = False
args = parser.parse_args(_processed_argv)
else:
subparsers.required = False
args = parser.parse_args(_processed_argv)
# Handle --version flag
if args.version:
cmd_version(args)
return
@@ -14028,37 +13813,9 @@ def main():
usage_file=getattr(args, "usage_file", None),
)
# Handle top-level --resume / --continue as shortcut to chat
if (args.resume or args.continue_last) and args.command is None:
args.command = "chat"
for attr, default in [
("query", None),
("model", None),
("provider", None),
("toolsets", None),
("verbose", None),
("worktree", False),
]:
if not hasattr(args, attr):
setattr(args, attr, default)
cmd_chat(args)
return
# Default to chat if no command specified
# No subcommand (optionally with top-level --resume / --continue) → chat.
if args.command is None:
for attr, default in [
("query", None),
("model", None),
("provider", None),
("toolsets", None),
("verbose", None),
("resume", None),
("continue_last", None),
("worktree", False),
]:
if not hasattr(args, attr):
setattr(args, attr, default)
cmd_chat(args)
_default_to_chat(args)
return
# Execute the command. Propagate the handler's return code as the