mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 16:01:49 +08:00
Pull the top-level + chat parser construction out of main() into hermes_cli/_parser.py so relaunch.py can introspect parser._actions to discover which flags exist and whether they take values, instead of maintaining a parallel hand-rolled (flag, takes_value) tuple list. - _parser.py: build_top_level_parser() returns (parser, subparsers, chat_parser); side-effect-free import. - main.py: ~290 lines of inline parser construction collapsed to a helper call. Other subparsers stay inline (dispatch is bound to module-level cmd_* functions). - test_ignore_user_config_flags.py: brittle inspect.getsource grep replaced with proper parser introspection. - test_relaunch.py: introspection sanity tests added. - _parser._inherited_flag(parser, ...): wraps parser.add_argument and sets action.inherit_on_relaunch = True. Used in place of parser.add_argument for the 25 flags (top-level + chat) that need to carry over. - _parser.PRE_ARGPARSE_INHERITED_FLAGS: holds --profile/-p, which isn't on argparse (consumed earlier by main._apply_profile_override). - relaunch.py: drops _CRITICAL_DESTS and _PRE_ARGPARSE_FLAGS; the table builder now filters by getattr(action, "inherit_on_relaunch", False). Renames _CRITICAL_FLAGS_TABLE / _build_critical_flag_table / _extract_critical_flags / preserve_critical= → _INHERITED_FLAGS_TABLE / _build_inherited_flag_table / _extract_inherited_flags / preserve_inherited= so the terminology is consistent.
368 lines
13 KiB
Python
368 lines
13 KiB
Python
"""
|
|
Top-level argparse construction for the hermes CLI.
|
|
|
|
Lives in its own module so other modules (e.g. ``relaunch.py``) can
|
|
introspect the parser to discover which flags exist without running the
|
|
``main`` fn.
|
|
|
|
Only the top-level parser and the ``chat`` subparser live here. Every other
|
|
subparser (model, gateway, sessions, …) is built inline in ``main.py``
|
|
because its dispatch is tightly coupled to module-level ``cmd_*`` functions.
|
|
"""
|
|
|
|
import argparse
|
|
|
|
|
|
# `--profile` / `-p` is consumed by ``main._apply_profile_override`` before
|
|
# argparse runs (it sets ``HERMES_HOME`` and strips itself from ``sys.argv``),
|
|
# so it isn't on the parser. Listed here so all "carry over on relaunch"
|
|
# metadata lives in one file.
|
|
PRE_ARGPARSE_INHERITED_FLAGS: list[tuple[str, bool]] = [
|
|
("--profile", True),
|
|
("-p", True),
|
|
]
|
|
|
|
|
|
def _inherited_flag(parser, *args, **kwargs):
|
|
"""Register a flag that ``hermes_cli.relaunch`` should carry over when
|
|
the CLI re-execs itself (e.g. after ``sessions browse`` picks a session,
|
|
or after the setup wizard launches chat).
|
|
|
|
Equivalent to ``parser.add_argument(...)`` plus tagging the resulting
|
|
Action with ``inherit_on_relaunch = True`` so the relaunch table builder
|
|
can find it via introspection.
|
|
"""
|
|
action = parser.add_argument(*args, **kwargs)
|
|
action.inherit_on_relaunch = True
|
|
return action
|
|
|
|
|
|
_EPILOGUE = """
|
|
Examples:
|
|
hermes Start interactive chat
|
|
hermes chat -q "Hello" Single query mode
|
|
hermes -c Resume the most recent session
|
|
hermes -c "my project" Resume a session by name (latest in lineage)
|
|
hermes --resume <session_id> Resume a specific session by ID
|
|
hermes setup Run setup wizard
|
|
hermes logout Clear stored authentication
|
|
hermes auth add <provider> Add a pooled credential
|
|
hermes auth list List pooled credentials
|
|
hermes auth remove <p> <t> Remove pooled credential by index, id, or label
|
|
hermes auth reset <provider> Clear exhaustion status for a provider
|
|
hermes model Select default model
|
|
hermes fallback [list] Show fallback provider chain
|
|
hermes fallback add Add a fallback provider (same picker as `hermes model`)
|
|
hermes fallback remove Remove a fallback provider from the chain
|
|
hermes config View configuration
|
|
hermes config edit Edit config in $EDITOR
|
|
hermes config set model gpt-4 Set a config value
|
|
hermes gateway Run messaging gateway
|
|
hermes -s hermes-agent-dev,github-auth
|
|
hermes -w Start in isolated git worktree
|
|
hermes gateway install Install gateway background service
|
|
hermes sessions list List past sessions
|
|
hermes sessions browse Interactive session picker
|
|
hermes sessions rename ID T Rename/title a session
|
|
hermes logs View agent.log (last 50 lines)
|
|
hermes logs -f Follow agent.log in real time
|
|
hermes logs errors View errors.log
|
|
hermes logs --since 1h Lines from the last hour
|
|
hermes debug share Upload debug report for support
|
|
hermes update Update to latest version
|
|
|
|
For more help on a command:
|
|
hermes <command> --help
|
|
"""
|
|
|
|
|
|
def build_top_level_parser():
|
|
"""Build the top-level parser, the subparsers action, and the ``chat`` subparser.
|
|
|
|
Returns ``(parser, subparsers, chat_parser)``. The caller wires
|
|
``chat_parser.set_defaults(func=cmd_chat)`` and continues registering
|
|
other subparsers via ``subparsers.add_parser(...)``.
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
prog="hermes",
|
|
description="Hermes Agent - AI assistant with tool-calling capabilities",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog=_EPILOGUE,
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--version", "-V", action="store_true", help="Show version and exit"
|
|
)
|
|
parser.add_argument(
|
|
"-z",
|
|
"--oneshot",
|
|
metavar="PROMPT",
|
|
default=None,
|
|
help=(
|
|
"One-shot mode: send a single prompt and print ONLY the final "
|
|
"response text to stdout. No banner, no spinner, no tool "
|
|
"previews, no session_id line. Tools, memory, rules, and "
|
|
"AGENTS.md in the CWD are loaded as normal; approvals are "
|
|
"auto-bypassed. Intended for scripts / pipes."
|
|
),
|
|
)
|
|
# --model / --provider are accepted at the top level so they can pair
|
|
# with -z without needing the `chat` subcommand. If neither -z nor a
|
|
# subcommand consumes them, they fall through harmlessly as None.
|
|
# Mirrors `hermes chat --model ... --provider ...` semantics.
|
|
_inherited_flag(
|
|
parser,
|
|
"-m",
|
|
"--model",
|
|
default=None,
|
|
help=(
|
|
"Model override for this invocation (e.g. anthropic/claude-sonnet-4.6). "
|
|
"Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_MODEL env var."
|
|
),
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--provider",
|
|
default=None,
|
|
help=(
|
|
"Provider override for this invocation (e.g. openrouter, anthropic). "
|
|
"Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_PROVIDER env var."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--resume",
|
|
"-r",
|
|
metavar="SESSION",
|
|
default=None,
|
|
help="Resume a previous session by ID or title",
|
|
)
|
|
parser.add_argument(
|
|
"--continue",
|
|
"-c",
|
|
dest="continue_last",
|
|
nargs="?",
|
|
const=True,
|
|
default=None,
|
|
metavar="SESSION_NAME",
|
|
help="Resume a session by name, or the most recent if no name given",
|
|
)
|
|
parser.add_argument(
|
|
"--worktree",
|
|
"-w",
|
|
action="store_true",
|
|
default=False,
|
|
help="Run in an isolated git worktree (for parallel agents)",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--accept-hooks",
|
|
action="store_true",
|
|
default=False,
|
|
help=(
|
|
"Auto-approve any unseen shell hooks declared in config.yaml "
|
|
"without a TTY prompt. Equivalent to HERMES_ACCEPT_HOOKS=1 or "
|
|
"hooks_auto_accept: true in config.yaml. Use on CI / headless "
|
|
"runs that can't prompt."
|
|
),
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--skills",
|
|
"-s",
|
|
action="append",
|
|
default=None,
|
|
help="Preload one or more skills for the session (repeat flag or comma-separate)",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--yolo",
|
|
action="store_true",
|
|
default=False,
|
|
help="Bypass all dangerous command approval prompts (use at your own risk)",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--pass-session-id",
|
|
action="store_true",
|
|
default=False,
|
|
help="Include the session ID in the agent's system prompt",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--ignore-user-config",
|
|
action="store_true",
|
|
default=False,
|
|
help="Ignore ~/.hermes/config.yaml and fall back to built-in defaults (credentials in .env are still loaded)",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--ignore-rules",
|
|
action="store_true",
|
|
default=False,
|
|
help="Skip auto-injection of AGENTS.md, SOUL.md, .cursorrules, memory, and preloaded skills",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--tui",
|
|
action="store_true",
|
|
default=False,
|
|
help="Launch the modern TUI instead of the classic REPL",
|
|
)
|
|
_inherited_flag(
|
|
parser,
|
|
"--dev",
|
|
dest="tui_dev",
|
|
action="store_true",
|
|
default=False,
|
|
help="With --tui: run TypeScript sources via tsx (skip dist build)",
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
|
|
# =========================================================================
|
|
# chat command
|
|
# =========================================================================
|
|
chat_parser = subparsers.add_parser(
|
|
"chat",
|
|
help="Interactive chat with the agent",
|
|
description="Start an interactive chat session with Hermes Agent",
|
|
)
|
|
chat_parser.add_argument(
|
|
"-q", "--query", help="Single query (non-interactive mode)"
|
|
)
|
|
chat_parser.add_argument(
|
|
"--image", help="Optional local image path to attach to a single query"
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"-m", "--model", help="Model to use (e.g., anthropic/claude-sonnet-4)",
|
|
)
|
|
chat_parser.add_argument(
|
|
"-t", "--toolsets", help="Comma-separated toolsets to enable"
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"-s",
|
|
"--skills",
|
|
action="append",
|
|
default=argparse.SUPPRESS,
|
|
help="Preload one or more skills for the session (repeat flag or comma-separate)",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--provider",
|
|
# No `choices=` here: user-defined providers from config.yaml `providers:`
|
|
# are also valid values, and runtime resolution (resolve_runtime_provider)
|
|
# handles validation/error reporting consistently with the top-level
|
|
# `--provider` flag.
|
|
default=None,
|
|
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"
|
|
)
|
|
chat_parser.add_argument(
|
|
"-Q",
|
|
"--quiet",
|
|
action="store_true",
|
|
help="Quiet mode for programmatic use: suppress banner, spinner, and tool previews. Only output the final response and session info.",
|
|
)
|
|
chat_parser.add_argument(
|
|
"--resume",
|
|
"-r",
|
|
metavar="SESSION_ID",
|
|
default=argparse.SUPPRESS,
|
|
help="Resume a previous session by ID (shown on exit)",
|
|
)
|
|
chat_parser.add_argument(
|
|
"--continue",
|
|
"-c",
|
|
dest="continue_last",
|
|
nargs="?",
|
|
const=True,
|
|
default=argparse.SUPPRESS,
|
|
metavar="SESSION_NAME",
|
|
help="Resume a session by name, or the most recent if no name given",
|
|
)
|
|
chat_parser.add_argument(
|
|
"--worktree",
|
|
"-w",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help="Run in an isolated git worktree (for parallel agents on the same repo)",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--accept-hooks",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help=(
|
|
"Auto-approve any unseen shell hooks declared in config.yaml "
|
|
"without a TTY prompt (see also HERMES_ACCEPT_HOOKS env var and "
|
|
"hooks_auto_accept: in config.yaml)."
|
|
),
|
|
)
|
|
chat_parser.add_argument(
|
|
"--checkpoints",
|
|
action="store_true",
|
|
default=False,
|
|
help="Enable filesystem checkpoints before destructive file operations (use /rollback to restore)",
|
|
)
|
|
chat_parser.add_argument(
|
|
"--max-turns",
|
|
type=int,
|
|
default=None,
|
|
metavar="N",
|
|
help="Maximum tool-calling iterations per conversation turn (default: 90, or agent.max_turns in config)",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--yolo",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help="Bypass all dangerous command approval prompts (use at your own risk)",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--pass-session-id",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help="Include the session ID in the agent's system prompt",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--ignore-user-config",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help="Ignore ~/.hermes/config.yaml and fall back to built-in defaults (credentials in .env are still loaded). Useful for isolated CI runs, reproduction, and third-party integrations.",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--ignore-rules",
|
|
action="store_true",
|
|
default=argparse.SUPPRESS,
|
|
help="Skip auto-injection of AGENTS.md, SOUL.md, .cursorrules, memory, and preloaded skills. Combine with --ignore-user-config for a fully isolated run.",
|
|
)
|
|
chat_parser.add_argument(
|
|
"--source",
|
|
default=None,
|
|
help="Session source tag for filtering (default: cli). Use 'tool' for third-party integrations that should not appear in user session lists.",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--tui",
|
|
action="store_true",
|
|
default=False,
|
|
help="Launch the modern TUI instead of the classic REPL",
|
|
)
|
|
_inherited_flag(
|
|
chat_parser,
|
|
"--dev",
|
|
dest="tui_dev",
|
|
action="store_true",
|
|
default=False,
|
|
help="With --tui: run TypeScript sources via tsx (skip dist build)",
|
|
)
|
|
|
|
return parser, subparsers, chat_parser
|