refactor(hermes_cli): compact _parser.py argparse construction (help bytes + action attrs identical)
This commit is contained in:
@@ -8,34 +8,19 @@ gateway, sessions, …) is built by ``hermes_cli/subcommands/<group>.py`` and wi
|
||||
import argparse
|
||||
from functools import lru_cache
|
||||
|
||||
# `--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)]
|
||||
|
||||
# `--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),
|
||||
]
|
||||
|
||||
|
||||
# Static snapshot fallback for ``top_level_value_flag_sets`` — used only if
|
||||
# introspecting the live parser fails (e.g. argparse surface broken mid-edit).
|
||||
# The derived path is authoritative; a parity test in
|
||||
# tests/hermes_cli/test_top_level_value_flags_parity.py fails CI if the parser
|
||||
# grows a value-taking flag this snapshot lacks AND derivation regresses.
|
||||
_VALUE_FLAGS_FALLBACK: frozenset[str] = frozenset(
|
||||
{
|
||||
"-z", "--oneshot",
|
||||
"-m", "--model",
|
||||
"--provider", "--reasoning",
|
||||
"-t", "--toolsets",
|
||||
"-r", "--resume",
|
||||
"-s", "--skills",
|
||||
"--usage-file",
|
||||
"--in",
|
||||
}
|
||||
)
|
||||
# Static snapshot fallback for ``top_level_value_flag_sets`` — used only if introspecting the
|
||||
# live parser fails. The derived path is authoritative; tests/hermes_cli/
|
||||
# test_top_level_value_flags_parity.py fails CI if the parser grows a value-taking flag this
|
||||
# snapshot lacks AND derivation regresses.
|
||||
_VALUE_FLAGS_FALLBACK: frozenset[str] = frozenset({
|
||||
"-z", "--oneshot", "-m", "--model", "--provider", "--reasoning", "-t", "--toolsets",
|
||||
"-r", "--resume", "-s", "--skills", "--usage-file", "--in",
|
||||
})
|
||||
_OPTIONAL_VALUE_FLAGS_FALLBACK: frozenset[str] = frozenset({"-c", "--continue"})
|
||||
|
||||
|
||||
@@ -45,8 +30,8 @@ def top_level_value_flag_sets() -> tuple[frozenset[str], frozenset[str]]:
|
||||
|
||||
Introspects ``build_top_level_parser()`` (every option with nargs != 0) so the argv scanners in
|
||||
``main.py`` (``_first_positional_argv``, ``_apply_profile_override``) can never drift from the
|
||||
argparse surface — the exact drift that made ``hermes --reasoning high chat …`` misread ``high``
|
||||
as the subcommand and forced eager plugin discovery (#93530).
|
||||
argparse surface — the drift that made ``hermes --reasoning high chat …`` misread ``high`` as
|
||||
the subcommand and forced eager plugin discovery.
|
||||
"""
|
||||
try:
|
||||
parser = build_top_level_parser()[0]
|
||||
@@ -63,11 +48,7 @@ def top_level_value_flag_sets() -> tuple[frozenset[str], frozenset[str]]:
|
||||
|
||||
|
||||
def _inherited_flag(parser, *args, **kwargs):
|
||||
"""Register a flag that ``hermes_cli.relaunch`` carries over when the CLI re-execs itself.
|
||||
|
||||
``parser.add_argument(...)`` plus tagging the Action with ``inherit_on_relaunch = True`` so
|
||||
the relaunch table builder can find it via introspection.
|
||||
"""
|
||||
"""``parser.add_argument`` + tag the Action ``inherit_on_relaunch`` for ``hermes_cli.relaunch``."""
|
||||
action = parser.add_argument(*args, **kwargs)
|
||||
action.inherit_on_relaunch = True
|
||||
return action
|
||||
@@ -122,462 +103,172 @@ For more help on a command:
|
||||
|
||||
def _add_top_level_flags(parser: argparse.ArgumentParser) -> None:
|
||||
"""Top-level (pre-subcommand) flags; ``-m/--provider`` pair with ``-z`` without ``chat``."""
|
||||
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."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--usage-file",
|
||||
metavar="PATH",
|
||||
default=None,
|
||||
help=(
|
||||
"One-shot mode only: after the run, write a JSON usage report "
|
||||
"(estimated cost, token counts, model, api_calls) to PATH. "
|
||||
"The report is written even when the run fails, so pipelines "
|
||||
"can always account for spend. No effect outside -z/--oneshot."
|
||||
),
|
||||
)
|
||||
# --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. The persistent provider lives in config.yaml "
|
||||
"under model.provider — use `hermes setup` or edit the file to change it."
|
||||
),
|
||||
)
|
||||
_inherited_flag(
|
||||
parser,
|
||||
"--reasoning",
|
||||
default=None,
|
||||
metavar="LEVEL",
|
||||
help=(
|
||||
"Reasoning effort for this invocation: none, minimal, low, medium, "
|
||||
"high, xhigh, max, or ultra. Overrides agent.reasoning_effort in "
|
||||
"config.yaml for this run only; the persistent level lives there "
|
||||
"(or per-model under agent.reasoning_overrides)."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--toolsets",
|
||||
default=None,
|
||||
help="Comma-separated toolsets to enable for this invocation. Applies to -z/--oneshot and --tui.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resume",
|
||||
"-r",
|
||||
metavar="SESSION",
|
||||
default=None,
|
||||
help=(
|
||||
"Resume a previous session by ID or title, or pass 'latest' for "
|
||||
"the most recent session (workspace-scoped, like -c with no name)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-restore-cwd",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Don't cd into a resumed session's recorded working directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--in",
|
||||
dest="in_dir",
|
||||
metavar="DIR",
|
||||
default=None,
|
||||
help=(
|
||||
"Change into DIR before starting or resuming. Combined with "
|
||||
"'--resume latest' or -c, the most recent session for DIR's "
|
||||
"workspace is picked, and the session stays in DIR (skips the "
|
||||
"recorded-cwd restore)."
|
||||
),
|
||||
)
|
||||
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,
|
||||
"--safe-mode",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Troubleshooting mode: disable ALL customizations — user config, AGENTS.md/memory injection, plugins, and MCP servers (implies --ignore-user-config and --ignore-rules)",
|
||||
)
|
||||
_inherited_flag(
|
||||
parser,
|
||||
"--tui",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Launch the modern TUI instead of the classic REPL",
|
||||
)
|
||||
_inherited_flag(
|
||||
parser,
|
||||
"--cli",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Force the classic prompt_toolkit REPL (overrides display.interface=tui)",
|
||||
)
|
||||
_inherited_flag(
|
||||
parser,
|
||||
"--dev",
|
||||
dest="tui_dev",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="With --tui: run TypeScript sources via tsx (skip dist build)",
|
||||
)
|
||||
add, inherited = parser.add_argument, _inherited_flag
|
||||
add("--version", "-V", action="store_true", help="Show version and exit")
|
||||
add("-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."))
|
||||
add("--usage-file", metavar="PATH", default=None, help=(
|
||||
"One-shot mode only: after the run, write a JSON usage report "
|
||||
"(estimated cost, token counts, model, api_calls) to PATH. "
|
||||
"The report is written even when the run fails, so pipelines "
|
||||
"can always account for spend. No effect outside -z/--oneshot."))
|
||||
# --model / --provider are accepted at the top level so they can pair with -z without the
|
||||
# `chat` subcommand; if neither -z nor a subcommand consumes them, they fall through as None.
|
||||
inherited(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(parser, "--provider", default=None, help=(
|
||||
"Provider override for this invocation (e.g. openrouter, anthropic). "
|
||||
"Applies to -z/--oneshot and --tui. The persistent provider lives in config.yaml "
|
||||
"under model.provider — use `hermes setup` or edit the file to change it."))
|
||||
inherited(parser, "--reasoning", default=None, metavar="LEVEL", help=(
|
||||
"Reasoning effort for this invocation: none, minimal, low, medium, "
|
||||
"high, xhigh, max, or ultra. Overrides agent.reasoning_effort in "
|
||||
"config.yaml for this run only; the persistent level lives there "
|
||||
"(or per-model under agent.reasoning_overrides)."))
|
||||
add("-t", "--toolsets", default=None,
|
||||
help="Comma-separated toolsets to enable for this invocation. Applies to -z/--oneshot and --tui.")
|
||||
add("--resume", "-r", metavar="SESSION", default=None, help=(
|
||||
"Resume a previous session by ID or title, or pass 'latest' for "
|
||||
"the most recent session (workspace-scoped, like -c with no name)"))
|
||||
add("--no-restore-cwd", action="store_true", default=False,
|
||||
help="Don't cd into a resumed session's recorded working directory.")
|
||||
add("--in", dest="in_dir", metavar="DIR", default=None, help=(
|
||||
"Change into DIR before starting or resuming. Combined with "
|
||||
"'--resume latest' or -c, the most recent session for DIR's "
|
||||
"workspace is picked, and the session stays in DIR (skips the "
|
||||
"recorded-cwd restore)."))
|
||||
add("--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")
|
||||
add("--worktree", "-w", action="store_true", default=False,
|
||||
help="Run in an isolated git worktree (for parallel agents)")
|
||||
inherited(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(parser, "--skills", "-s", action="append", default=None,
|
||||
help="Preload one or more skills for the session (repeat flag or comma-separate)")
|
||||
inherited(parser, "--yolo", action="store_true", default=False,
|
||||
help="Bypass all dangerous command approval prompts (use at your own risk)")
|
||||
inherited(parser, "--pass-session-id", action="store_true", default=False,
|
||||
help="Include the session ID in the agent's system prompt")
|
||||
inherited(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(parser, "--ignore-rules", action="store_true", default=False,
|
||||
help="Skip auto-injection of AGENTS.md, SOUL.md, .cursorrules, memory, and preloaded skills")
|
||||
inherited(parser, "--safe-mode", action="store_true", default=False,
|
||||
help="Troubleshooting mode: disable ALL customizations — user config, AGENTS.md/memory injection, plugins, and MCP servers (implies --ignore-user-config and --ignore-rules)")
|
||||
inherited(parser, "--tui", action="store_true", default=False,
|
||||
help="Launch the modern TUI instead of the classic REPL")
|
||||
inherited(parser, "--cli", action="store_true", default=False,
|
||||
help="Force the classic prompt_toolkit REPL (overrides display.interface=tui)")
|
||||
inherited(parser, "--dev", dest="tui_dev", action="store_true", default=False,
|
||||
help="With --tui: run TypeScript sources via tsx (skip dist build)")
|
||||
|
||||
|
||||
def _build_chat_parser(subparsers) -> argparse.ArgumentParser:
|
||||
"""The ``chat`` subparser (also the implicit default command)."""
|
||||
"""The ``chat`` subparser (also the implicit default command).
|
||||
|
||||
Flags ALSO declared on the top-level parser use ``default=argparse.SUPPRESS``: for
|
||||
``hermes -m foo chat`` argparse first sets ``args.model`` from the top-level parser, then
|
||||
dispatches to the chat subparser, which shares the namespace and ``dest`` — a plain ``None``
|
||||
default would silently clobber the top-level value. SUPPRESS keeps the subparser action a no-op
|
||||
unless the flag is actually passed after the subcommand (tests/hermes_cli/
|
||||
test_argparse_flag_propagation.py).
|
||||
"""
|
||||
chat_parser = subparsers.add_parser(
|
||||
"chat",
|
||||
help="Interactive chat with the agent",
|
||||
description="Start an interactive chat session with Hermes Agent",
|
||||
)
|
||||
"chat", help="Interactive chat with the agent",
|
||||
description="Start an interactive chat session with Hermes Agent")
|
||||
add, inherited, SUPPRESS = chat_parser.add_argument, _inherited_flag, argparse.SUPPRESS
|
||||
_query_group = chat_parser.add_mutually_exclusive_group()
|
||||
_query_group.add_argument(
|
||||
"-q", "--query",
|
||||
help=(
|
||||
"Query to run. On a real TTY the prompt seeds an interactive "
|
||||
"session (submitted literally as the first turn); combined with "
|
||||
"--oneshot or -Q, or on a non-TTY, it answers and exits."
|
||||
),
|
||||
)
|
||||
_query_group.add_argument(
|
||||
"--query-file",
|
||||
metavar="PATH",
|
||||
help=(
|
||||
"Read the single query from a file instead of the command line "
|
||||
"('-' reads stdin). Safe for arbitrary text: nothing is shell-"
|
||||
"interpreted, so quotes, $(...), and backticks are preserved "
|
||||
"verbatim. Mutually exclusive with -q."
|
||||
),
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"--oneshot",
|
||||
dest="oneshot_exit",
|
||||
action="store_true",
|
||||
# Distinct dest: the top-level `-z/--oneshot PROMPT` is value-taking
|
||||
# and its dispatch sites do `if args.oneshot: _run_and_exit_oneshot(
|
||||
# args.oneshot)` — a shared boolean dest would be passed as the
|
||||
# prompt. `oneshot_exit` keeps the surfaces independent.
|
||||
default=False,
|
||||
help=(
|
||||
"With -q/--query-file: answer the query and exit (legacy "
|
||||
"single-query behavior) instead of seeding an interactive "
|
||||
"session. Implied on non-TTY stdio and by -Q/--quiet."
|
||||
),
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"--image", help="Optional local image path to attach to a single query"
|
||||
)
|
||||
# `default=argparse.SUPPRESS` on flags that are ALSO declared on the
|
||||
# top-level parser: when the user writes `hermes -m foo chat`, argparse
|
||||
# first sets `args.model = "foo"` from the top-level parser, then
|
||||
# dispatches to the chat subparser. Without SUPPRESS the chat subparser's
|
||||
# own default (`None`) would silently clobber the top-level value because
|
||||
# the subparser shares the same namespace and `dest`. SUPPRESS keeps the
|
||||
# subparser action a no-op unless the user actually passes the flag after
|
||||
# the subcommand. Matches the pattern already used for `-s/--skills` and
|
||||
# the relaunch-inherited flags `-r/--resume`, `-c/--continue`,
|
||||
# `-w/--worktree`, `--yolo`, etc. (see tests/hermes_cli/
|
||||
# test_argparse_flag_propagation.py).
|
||||
_inherited_flag(
|
||||
chat_parser,
|
||||
"-m", "--model",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Model to use (e.g., anthropic/claude-sonnet-4)",
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"-t", "--toolsets",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Comma-separated toolsets to enable",
|
||||
)
|
||||
_inherited_flag(
|
||||
chat_parser,
|
||||
"--reasoning",
|
||||
default=argparse.SUPPRESS,
|
||||
metavar="LEVEL",
|
||||
help=(
|
||||
"Reasoning effort for this session: none, minimal, low, medium, "
|
||||
"high, xhigh, max, or ultra. Overrides agent.reasoning_effort for "
|
||||
"this run only (same levels as the /reasoning slash command)."
|
||||
),
|
||||
)
|
||||
_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=argparse.SUPPRESS,
|
||||
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",
|
||||
default=argparse.SUPPRESS,
|
||||
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), or 'latest' "
|
||||
"for the most recent session"
|
||||
),
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"--no-restore-cwd",
|
||||
action="store_true",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Don't cd into a resumed session's recorded working directory.",
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"--in",
|
||||
dest="in_dir",
|
||||
metavar="DIR",
|
||||
default=argparse.SUPPRESS,
|
||||
help=(
|
||||
"Change into DIR before starting or resuming (scopes "
|
||||
"'--resume latest' / -c lookups to DIR's workspace)."
|
||||
),
|
||||
)
|
||||
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(
|
||||
"--create-if-missing",
|
||||
action="store_true",
|
||||
default=argparse.SUPPRESS,
|
||||
help=(
|
||||
"With -c/--continue <name>: if no session matches the name, "
|
||||
"create a new session with that title and proceed (instead of "
|
||||
"failing with a not-found error). Programmatic callers that "
|
||||
"want 'send to this named thread, making it if needed'."
|
||||
),
|
||||
)
|
||||
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: 500, or agent.max_turns in config)",
|
||||
)
|
||||
chat_parser.add_argument(
|
||||
"--run-budget",
|
||||
type=float,
|
||||
default=None,
|
||||
metavar="SECONDS",
|
||||
dest="run_budget",
|
||||
help=(
|
||||
"Optional wall-clock budget in seconds for each conversation run. "
|
||||
"At 80%% elapsed the agent gets a one-time wrap-up notice, and "
|
||||
"implicit provider stale timeouts are capped to the remaining "
|
||||
"budget so one hung call can't consume the run. Unset = off. "
|
||||
"Also configurable as agent.run_budget_seconds in config.yaml. "
|
||||
"Intended for one-shot/eval invocations with a hard ceiling."
|
||||
),
|
||||
)
|
||||
_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.",
|
||||
)
|
||||
_inherited_flag(
|
||||
chat_parser,
|
||||
"--safe-mode",
|
||||
action="store_true",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Troubleshooting mode: disable ALL customizations — user config, AGENTS.md/memory injection, plugins, and MCP servers (implies --ignore-user-config and --ignore-rules). Use to isolate whether a problem comes from your setup or from Hermes itself.",
|
||||
)
|
||||
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=argparse.SUPPRESS,
|
||||
help="Launch the modern TUI instead of the classic REPL",
|
||||
)
|
||||
_inherited_flag(
|
||||
chat_parser,
|
||||
"--cli",
|
||||
action="store_true",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Force the classic prompt_toolkit REPL (overrides display.interface=tui)",
|
||||
)
|
||||
_inherited_flag(
|
||||
chat_parser,
|
||||
"--dev",
|
||||
dest="tui_dev",
|
||||
action="store_true",
|
||||
default=argparse.SUPPRESS,
|
||||
help="With --tui: run TypeScript sources via tsx (skip dist build)",
|
||||
)
|
||||
_query_group.add_argument("-q", "--query", help=(
|
||||
"Query to run. On a real TTY the prompt seeds an interactive "
|
||||
"session (submitted literally as the first turn); combined with "
|
||||
"--oneshot or -Q, or on a non-TTY, it answers and exits."))
|
||||
_query_group.add_argument("--query-file", metavar="PATH", help=(
|
||||
"Read the single query from a file instead of the command line "
|
||||
"('-' reads stdin). Safe for arbitrary text: nothing is shell-"
|
||||
"interpreted, so quotes, $(...), and backticks are preserved "
|
||||
"verbatim. Mutually exclusive with -q."))
|
||||
# Distinct dest: the top-level `-z/--oneshot PROMPT` is value-taking and its dispatch sites do
|
||||
# `if args.oneshot: _run_and_exit_oneshot(args.oneshot)` — a shared boolean dest would be
|
||||
# passed as the prompt.
|
||||
add("--oneshot", dest="oneshot_exit", action="store_true", default=False, help=(
|
||||
"With -q/--query-file: answer the query and exit (legacy "
|
||||
"single-query behavior) instead of seeding an interactive "
|
||||
"session. Implied on non-TTY stdio and by -Q/--quiet."))
|
||||
add("--image", help="Optional local image path to attach to a single query")
|
||||
inherited(chat_parser, "-m", "--model", default=SUPPRESS,
|
||||
help="Model to use (e.g., anthropic/claude-sonnet-4)")
|
||||
add("-t", "--toolsets", default=SUPPRESS, help="Comma-separated toolsets to enable")
|
||||
inherited(chat_parser, "--reasoning", default=SUPPRESS, metavar="LEVEL", help=(
|
||||
"Reasoning effort for this session: none, minimal, low, medium, "
|
||||
"high, xhigh, max, or ultra. Overrides agent.reasoning_effort for "
|
||||
"this run only (same levels as the /reasoning slash command)."))
|
||||
inherited(chat_parser, "-s", "--skills", action="append", default=SUPPRESS,
|
||||
help="Preload one or more skills for the session (repeat flag or comma-separate)")
|
||||
# No `choices=` on --provider: user-defined providers from config.yaml `providers:` are valid
|
||||
# too; runtime resolution (resolve_runtime_provider) validates, same as the top-level flag.
|
||||
inherited(chat_parser, "--provider", default=SUPPRESS,
|
||||
help="Inference provider (default: auto). Built-in or a user-defined name from `providers:` in config.yaml.")
|
||||
add("-v", "--verbose", action="store_true", default=SUPPRESS, help="Verbose output")
|
||||
add("-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.")
|
||||
add("--resume", "-r", metavar="SESSION_ID", default=SUPPRESS, help=(
|
||||
"Resume a previous session by ID (shown on exit), or 'latest' "
|
||||
"for the most recent session"))
|
||||
add("--no-restore-cwd", action="store_true", default=SUPPRESS,
|
||||
help="Don't cd into a resumed session's recorded working directory.")
|
||||
add("--in", dest="in_dir", metavar="DIR", default=SUPPRESS, help=(
|
||||
"Change into DIR before starting or resuming (scopes "
|
||||
"'--resume latest' / -c lookups to DIR's workspace)."))
|
||||
add("--continue", "-c", dest="continue_last", nargs="?", const=True, default=SUPPRESS,
|
||||
metavar="SESSION_NAME", help="Resume a session by name, or the most recent if no name given")
|
||||
add("--create-if-missing", action="store_true", default=SUPPRESS, help=(
|
||||
"With -c/--continue <name>: if no session matches the name, "
|
||||
"create a new session with that title and proceed (instead of "
|
||||
"failing with a not-found error). Programmatic callers that "
|
||||
"want 'send to this named thread, making it if needed'."))
|
||||
add("--worktree", "-w", action="store_true", default=SUPPRESS,
|
||||
help="Run in an isolated git worktree (for parallel agents on the same repo)")
|
||||
inherited(chat_parser, "--accept-hooks", action="store_true", default=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)."))
|
||||
add("--checkpoints", action="store_true", default=False,
|
||||
help="Enable filesystem checkpoints before destructive file operations (use /rollback to restore)")
|
||||
add("--max-turns", type=int, default=None, metavar="N",
|
||||
help="Maximum tool-calling iterations per conversation turn (default: 500, or agent.max_turns in config)")
|
||||
add("--run-budget", type=float, default=None, metavar="SECONDS", dest="run_budget", help=(
|
||||
"Optional wall-clock budget in seconds for each conversation run. "
|
||||
"At 80%% elapsed the agent gets a one-time wrap-up notice, and "
|
||||
"implicit provider stale timeouts are capped to the remaining "
|
||||
"budget so one hung call can't consume the run. Unset = off. "
|
||||
"Also configurable as agent.run_budget_seconds in config.yaml. "
|
||||
"Intended for one-shot/eval invocations with a hard ceiling."))
|
||||
inherited(chat_parser, "--yolo", action="store_true", default=SUPPRESS,
|
||||
help="Bypass all dangerous command approval prompts (use at your own risk)")
|
||||
inherited(chat_parser, "--pass-session-id", action="store_true", default=SUPPRESS,
|
||||
help="Include the session ID in the agent's system prompt")
|
||||
inherited(chat_parser, "--ignore-user-config", action="store_true", default=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(chat_parser, "--ignore-rules", action="store_true", default=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.")
|
||||
inherited(chat_parser, "--safe-mode", action="store_true", default=SUPPRESS,
|
||||
help="Troubleshooting mode: disable ALL customizations — user config, AGENTS.md/memory injection, plugins, and MCP servers (implies --ignore-user-config and --ignore-rules). Use to isolate whether a problem comes from your setup or from Hermes itself.")
|
||||
add("--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(chat_parser, "--tui", action="store_true", default=SUPPRESS,
|
||||
help="Launch the modern TUI instead of the classic REPL")
|
||||
inherited(chat_parser, "--cli", action="store_true", default=SUPPRESS,
|
||||
help="Force the classic prompt_toolkit REPL (overrides display.interface=tui)")
|
||||
inherited(chat_parser, "--dev", dest="tui_dev", action="store_true", default=SUPPRESS,
|
||||
help="With --tui: run TypeScript sources via tsx (skip dist build)")
|
||||
return chat_parser
|
||||
|
||||
|
||||
@@ -589,15 +280,8 @@ def build_top_level_parser():
|
||||
``subparsers.add_parser(...)``.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="hermes",
|
||||
description="Hermes Agent - AI assistant with tool-calling capabilities",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=_EPILOGUE,
|
||||
)
|
||||
|
||||
prog="hermes", description="Hermes Agent - AI assistant with tool-calling capabilities",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter, epilog=_EPILOGUE)
|
||||
_add_top_level_flags(parser)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
||||
chat_parser = _build_chat_parser(subparsers)
|
||||
|
||||
return parser, subparsers, chat_parser
|
||||
return parser, subparsers, _build_chat_parser(subparsers)
|
||||
|
||||
Reference in New Issue
Block a user