/ hermes_cli / _parser.py
_parser.py
  1  """
  2  Top-level argparse construction for the hermes CLI.
  3  
  4  Lives in its own module so other modules (e.g. ``relaunch.py``) can
  5  introspect the parser to discover which flags exist without running the
  6  ``main`` fn.
  7  
  8  Only the top-level parser and the ``chat`` subparser live here. Every other
  9  subparser (model, gateway, sessions, …) is built inline in ``main.py``
 10  because its dispatch is tightly coupled to module-level ``cmd_*`` functions.
 11  """
 12  
 13  import argparse
 14  
 15  
 16  # `--profile` / `-p` is consumed by ``main._apply_profile_override`` before
 17  # argparse runs (it sets ``HERMES_HOME`` and strips itself from ``sys.argv``),
 18  # so it isn't on the parser. Listed here so all "carry over on relaunch"
 19  # metadata lives in one file.
 20  PRE_ARGPARSE_INHERITED_FLAGS: list[tuple[str, bool]] = [
 21      ("--profile", True),
 22      ("-p", True),
 23  ]
 24  
 25  
 26  def _inherited_flag(parser, *args, **kwargs):
 27      """Register a flag that ``hermes_cli.relaunch`` should carry over when
 28      the CLI re-execs itself (e.g. after ``sessions browse`` picks a session,
 29      or after the setup wizard launches chat).
 30  
 31      Equivalent to ``parser.add_argument(...)`` plus tagging the resulting
 32      Action with ``inherit_on_relaunch = True`` so the relaunch table builder
 33      can find it via introspection.
 34      """
 35      action = parser.add_argument(*args, **kwargs)
 36      action.inherit_on_relaunch = True
 37      return action
 38  
 39  
 40  _EPILOGUE = """
 41  Examples:
 42      hermes                        Start interactive chat
 43      hermes chat -q "Hello"        Single query mode
 44      hermes -c                     Resume the most recent session
 45      hermes -c "my project"        Resume a session by name (latest in lineage)
 46      hermes --resume <session_id>  Resume a specific session by ID
 47      hermes setup                  Run setup wizard
 48      hermes logout                 Clear stored authentication
 49      hermes auth add <provider>    Add a pooled credential
 50      hermes auth list              List pooled credentials
 51      hermes auth remove <p> <t>    Remove pooled credential by index, id, or label
 52      hermes auth reset <provider>  Clear exhaustion status for a provider
 53      hermes model                  Select default model
 54      hermes fallback [list]        Show fallback provider chain
 55      hermes fallback add           Add a fallback provider (same picker as `hermes model`)
 56      hermes fallback remove        Remove a fallback provider from the chain
 57      hermes config                 View configuration
 58      hermes config edit            Edit config in $EDITOR
 59      hermes config set model gpt-4 Set a config value
 60      hermes gateway                Run messaging gateway
 61      hermes -s hermes-agent-dev,github-auth
 62      hermes -w                     Start in isolated git worktree
 63      hermes gateway install        Install gateway background service
 64      hermes sessions list          List past sessions
 65      hermes sessions browse        Interactive session picker
 66      hermes sessions rename ID T   Rename/title a session
 67      hermes logs                   View agent.log (last 50 lines)
 68      hermes logs -f                Follow agent.log in real time
 69      hermes logs errors            View errors.log
 70      hermes logs --since 1h        Lines from the last hour
 71      hermes debug share             Upload debug report for support
 72      hermes update                 Update to latest version
 73  
 74  For more help on a command:
 75      hermes <command> --help
 76  """
 77  
 78  
 79  def build_top_level_parser():
 80      """Build the top-level parser, the subparsers action, and the ``chat`` subparser.
 81  
 82      Returns ``(parser, subparsers, chat_parser)``. The caller wires
 83      ``chat_parser.set_defaults(func=cmd_chat)`` and continues registering
 84      other subparsers via ``subparsers.add_parser(...)``.
 85      """
 86      parser = argparse.ArgumentParser(
 87          prog="hermes",
 88          description="Hermes Agent - AI assistant with tool-calling capabilities",
 89          formatter_class=argparse.RawDescriptionHelpFormatter,
 90          epilog=_EPILOGUE,
 91      )
 92  
 93      parser.add_argument(
 94          "--version", "-V", action="store_true", help="Show version and exit"
 95      )
 96      parser.add_argument(
 97          "-z",
 98          "--oneshot",
 99          metavar="PROMPT",
100          default=None,
101          help=(
102              "One-shot mode: send a single prompt and print ONLY the final "
103              "response text to stdout. No banner, no spinner, no tool "
104              "previews, no session_id line. Tools, memory, rules, and "
105              "AGENTS.md in the CWD are loaded as normal; approvals are "
106              "auto-bypassed. Intended for scripts / pipes."
107          ),
108      )
109      # --model / --provider are accepted at the top level so they can pair
110      # with -z without needing the `chat` subcommand.  If neither -z nor a
111      # subcommand consumes them, they fall through harmlessly as None.
112      # Mirrors `hermes chat --model ... --provider ...` semantics.
113      _inherited_flag(
114          parser,
115          "-m",
116          "--model",
117          default=None,
118          help=(
119              "Model override for this invocation (e.g. anthropic/claude-sonnet-4.6). "
120              "Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_MODEL env var."
121          ),
122      )
123      _inherited_flag(
124          parser,
125          "--provider",
126          default=None,
127          help=(
128              "Provider override for this invocation (e.g. openrouter, anthropic). "
129              "Applies to -z/--oneshot and --tui. Also settable via HERMES_INFERENCE_PROVIDER env var."
130          ),
131      )
132      parser.add_argument(
133          "-t",
134          "--toolsets",
135          default=None,
136          help="Comma-separated toolsets to enable for this invocation. Applies to -z/--oneshot and --tui.",
137      )
138      parser.add_argument(
139          "--resume",
140          "-r",
141          metavar="SESSION",
142          default=None,
143          help="Resume a previous session by ID or title",
144      )
145      parser.add_argument(
146          "--continue",
147          "-c",
148          dest="continue_last",
149          nargs="?",
150          const=True,
151          default=None,
152          metavar="SESSION_NAME",
153          help="Resume a session by name, or the most recent if no name given",
154      )
155      parser.add_argument(
156          "--worktree",
157          "-w",
158          action="store_true",
159          default=False,
160          help="Run in an isolated git worktree (for parallel agents)",
161      )
162      _inherited_flag(
163          parser,
164          "--accept-hooks",
165          action="store_true",
166          default=False,
167          help=(
168              "Auto-approve any unseen shell hooks declared in config.yaml "
169              "without a TTY prompt.  Equivalent to HERMES_ACCEPT_HOOKS=1 or "
170              "hooks_auto_accept: true in config.yaml.  Use on CI / headless "
171              "runs that can't prompt."
172          ),
173      )
174      _inherited_flag(
175          parser,
176          "--skills",
177          "-s",
178          action="append",
179          default=None,
180          help="Preload one or more skills for the session (repeat flag or comma-separate)",
181      )
182      _inherited_flag(
183          parser,
184          "--yolo",
185          action="store_true",
186          default=False,
187          help="Bypass all dangerous command approval prompts (use at your own risk)",
188      )
189      _inherited_flag(
190          parser,
191          "--pass-session-id",
192          action="store_true",
193          default=False,
194          help="Include the session ID in the agent's system prompt",
195      )
196      _inherited_flag(
197          parser,
198          "--ignore-user-config",
199          action="store_true",
200          default=False,
201          help="Ignore ~/.hermes/config.yaml and fall back to built-in defaults (credentials in .env are still loaded)",
202      )
203      _inherited_flag(
204          parser,
205          "--ignore-rules",
206          action="store_true",
207          default=False,
208          help="Skip auto-injection of AGENTS.md, SOUL.md, .cursorrules, memory, and preloaded skills",
209      )
210      _inherited_flag(
211          parser,
212          "--tui",
213          action="store_true",
214          default=False,
215          help="Launch the modern TUI instead of the classic REPL",
216      )
217      _inherited_flag(
218          parser,
219          "--dev",
220          dest="tui_dev",
221          action="store_true",
222          default=False,
223          help="With --tui: run TypeScript sources via tsx (skip dist build)",
224      )
225  
226      subparsers = parser.add_subparsers(dest="command", help="Command to run")
227  
228      # =========================================================================
229      # chat command
230      # =========================================================================
231      chat_parser = subparsers.add_parser(
232          "chat",
233          help="Interactive chat with the agent",
234          description="Start an interactive chat session with Hermes Agent",
235      )
236      chat_parser.add_argument(
237          "-q", "--query", help="Single query (non-interactive mode)"
238      )
239      chat_parser.add_argument(
240          "--image", help="Optional local image path to attach to a single query"
241      )
242      _inherited_flag(
243          chat_parser,
244          "-m", "--model", help="Model to use (e.g., anthropic/claude-sonnet-4)",
245      )
246      chat_parser.add_argument(
247          "-t", "--toolsets", help="Comma-separated toolsets to enable"
248      )
249      _inherited_flag(
250          chat_parser,
251          "-s",
252          "--skills",
253          action="append",
254          default=argparse.SUPPRESS,
255          help="Preload one or more skills for the session (repeat flag or comma-separate)",
256      )
257      _inherited_flag(
258          chat_parser,
259          "--provider",
260          # No `choices=` here: user-defined providers from config.yaml `providers:`
261          # are also valid values, and runtime resolution (resolve_runtime_provider)
262          # handles validation/error reporting consistently with the top-level
263          # `--provider` flag.
264          default=None,
265          help="Inference provider (default: auto). Built-in or a user-defined name from `providers:` in config.yaml.",
266      )
267      chat_parser.add_argument(
268          "-v", "--verbose", action="store_true", help="Verbose output"
269      )
270      chat_parser.add_argument(
271          "-Q",
272          "--quiet",
273          action="store_true",
274          help="Quiet mode for programmatic use: suppress banner, spinner, and tool previews. Only output the final response and session info.",
275      )
276      chat_parser.add_argument(
277          "--resume",
278          "-r",
279          metavar="SESSION_ID",
280          default=argparse.SUPPRESS,
281          help="Resume a previous session by ID (shown on exit)",
282      )
283      chat_parser.add_argument(
284          "--continue",
285          "-c",
286          dest="continue_last",
287          nargs="?",
288          const=True,
289          default=argparse.SUPPRESS,
290          metavar="SESSION_NAME",
291          help="Resume a session by name, or the most recent if no name given",
292      )
293      chat_parser.add_argument(
294          "--worktree",
295          "-w",
296          action="store_true",
297          default=argparse.SUPPRESS,
298          help="Run in an isolated git worktree (for parallel agents on the same repo)",
299      )
300      _inherited_flag(
301          chat_parser,
302          "--accept-hooks",
303          action="store_true",
304          default=argparse.SUPPRESS,
305          help=(
306              "Auto-approve any unseen shell hooks declared in config.yaml "
307              "without a TTY prompt (see also HERMES_ACCEPT_HOOKS env var and "
308              "hooks_auto_accept: in config.yaml)."
309          ),
310      )
311      chat_parser.add_argument(
312          "--checkpoints",
313          action="store_true",
314          default=False,
315          help="Enable filesystem checkpoints before destructive file operations (use /rollback to restore)",
316      )
317      chat_parser.add_argument(
318          "--max-turns",
319          type=int,
320          default=None,
321          metavar="N",
322          help="Maximum tool-calling iterations per conversation turn (default: 90, or agent.max_turns in config)",
323      )
324      _inherited_flag(
325          chat_parser,
326          "--yolo",
327          action="store_true",
328          default=argparse.SUPPRESS,
329          help="Bypass all dangerous command approval prompts (use at your own risk)",
330      )
331      _inherited_flag(
332          chat_parser,
333          "--pass-session-id",
334          action="store_true",
335          default=argparse.SUPPRESS,
336          help="Include the session ID in the agent's system prompt",
337      )
338      _inherited_flag(
339          chat_parser,
340          "--ignore-user-config",
341          action="store_true",
342          default=argparse.SUPPRESS,
343          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.",
344      )
345      _inherited_flag(
346          chat_parser,
347          "--ignore-rules",
348          action="store_true",
349          default=argparse.SUPPRESS,
350          help="Skip auto-injection of AGENTS.md, SOUL.md, .cursorrules, memory, and preloaded skills. Combine with --ignore-user-config for a fully isolated run.",
351      )
352      chat_parser.add_argument(
353          "--source",
354          default=None,
355          help="Session source tag for filtering (default: cli). Use 'tool' for third-party integrations that should not appear in user session lists.",
356      )
357      _inherited_flag(
358          chat_parser,
359          "--tui",
360          action="store_true",
361          default=False,
362          help="Launch the modern TUI instead of the classic REPL",
363      )
364      _inherited_flag(
365          chat_parser,
366          "--dev",
367          dest="tui_dev",
368          action="store_true",
369          default=False,
370          help="With --tui: run TypeScript sources via tsx (skip dist build)",
371      )
372  
373      return parser, subparsers, chat_parser