Adds a bounded, read-only health probe per CONFIGURED tool backend, run
only when the user explicitly passes `--live` (real network calls):
- Firecrawl: credit-usage metadata GET (auth check, no scrape spend)
- FAL: models metadata GET (never a generation)
- Browser: headless launch + about:blank + close (full cleanup)
- MCP: initialize + tools/list per configured server (reuses the
`hermes mcp test` machinery in mcp_config._probe_single_server)
- TTS/STT: provider models/voices list GET (openai/groq/elevenlabs);
local providers (edge/piper/faster-whisper/...) skipped
Invariants:
- Opt-in only: zero probes without --live (default False)
- Bounded: sequential, per-probe timeout (doctor.live_probe_timeout,
default 10s, config.yaml knob)
- Never mutates state; unconfigured backends skip with a note
- Failure isolation: every probe wrapped in a catch-all; a probe crash
can never break the doctor run; failures append to the issues summary
New: hermes_cli/doctor_live.py, tests/hermes_cli/test_doctor_live.py
(23 tests, probes mocked at the HTTP/client seam).
Wired: --live flag in subcommands/doctor.py; run_doctor calls
maybe_run_live_checks after all static checks.
Coordination: PR #70124 (--probe-routes) probes LLM routes; this flag
probes TOOL backends — different surface, no code-region collision
(the run_doctor hook here sits at the end-of-run summary, not the
API Connectivity section #70124 extends).
Inspired by: paradigmxyz/centaur tool-health-smoke (MIT/Apache-2.0);
sibling: #70124 (LLM route probes — different surface)
Batch extraction of every remaining subcommand whose handler is top-level and
whose parser block is pure argparse: model, setup, postinstall, whatsapp, slack,
login, logout, auth, status, webhook, hooks, doctor, security, dump, debug,
backup, import, config, version, update, uninstall, dashboard, gui, logs,
prompt-size.
Each becomes hermes_cli/subcommands/<name>.py with build_<name>_parser() and an
injected handler (no main import). dashboard also injects cmd_dashboard_register
for its nested 'register' action.
Behavior-neutral: all 25 subcommands' --help output (and nested subaction help)
diff-verified byte-identical to pre-extraction. Two RawDescriptionHelpFormatter
epilogs (debug, logs) needed their multi-line string interiors preserved at
column 0 — caught by the --help diff, not compile.
main() 3297 -> 1798 LOC across this PR; add_parser calls in main.py 179 -> 89.
Validation: tests/hermes_cli/ 6476 passed / 0 failed under per-file process
isolation; new test_subcommands_batch.py smoke-tests all 25 builders + the
dashboard two-handler case.