/ 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