fix(cli): harden hermes portal SystemExit handling + finish model-pick doc sweep

Self-review of #38465 surfaced three real items:

1. SystemExit escape (defense): `_login_nous` raises SystemExit(130)/(1) on
   cancel/failure. The logged-out login path inside `_model_flow_nous` catches
   it, but the expired-session re-login path (main.py) only catches Exception,
   so a Ctrl-C during re-auth could propagate past `_run_portal_one_shot` and
   kill the CLI. Add SystemExit to the portal handler so all cancel/abort cases
   end with the graceful 'Setup cancelled / retry later' message.

2. Doc sweep: the model-pick step was only added to the bare-`hermes portal`
   prose. Propagate it to the surfaces describing `hermes setup --portal`
   behavior that still omitted model selection:
   - `--portal` argparse help (main.py)
   - nous-portal.md intro + the numbered 'what it does' step list (EN + zh-Hans)
   - run-hermes-with-nous-portal.md 'default model after setup --portal' line,
     which was now contradictory (there's a picker, not a forced default) (EN + zh)

3. Test coverage: add parametrized regression test asserting the portal handler
   swallows KeyboardInterrupt / EOFError / SystemExit (returns None, no escape).

Note on 'Skip (keep current)': delegating to _model_flow_nous means picking
Skip preserves the prior provider instead of force-switching to nous — this is
intentional and matches quick setup exactly; docs now say 'sets Nous as your
provider (when you pick a model)' rather than unconditionally.
This commit is contained in:
kshitijk4poor
2026-06-04 02:33:33 +05:30
parent cd188b814e
commit 26a57467a8
7 changed files with 41 additions and 14 deletions

View File

@ -12681,9 +12681,9 @@ def main():
setup_parser.add_argument( setup_parser.add_argument(
"--portal", "--portal",
action="store_true", action="store_true",
help="One-shot Nous Portal setup: log in via OAuth, set Nous as the " help="One-shot Nous Portal setup: log in via OAuth, pick a Nous "
"inference provider, and opt into the Tool Gateway. Skips the " "model, set Nous as the inference provider, and opt into the Tool "
"rest of the wizard.", "Gateway. Skips the rest of the wizard.",
) )
setup_parser.set_defaults(func=cmd_setup) setup_parser.set_defaults(func=cmd_setup)

View File

@ -2770,7 +2770,12 @@ def _run_portal_one_shot(config: dict) -> None:
from hermes_cli.main import _model_flow_nous from hermes_cli.main import _model_flow_nous
_model_flow_nous(config) _model_flow_nous(config)
except (KeyboardInterrupt, EOFError): except (KeyboardInterrupt, EOFError, SystemExit):
# _login_nous raises SystemExit(130)/(1) on cancel/failure; the
# logged-out path inside _model_flow_nous catches it, but the
# expired-session re-login path only catches Exception, so a
# SystemExit there would otherwise escape and kill the whole CLI.
# Treat all of these as a graceful cancel/abort for the portal flow.
print() print()
print_info(" Setup cancelled.") print_info(" Setup cancelled.")
print_info(" You can retry later with `hermes portal`.") print_info(" You can retry later with `hermes portal`.")

View File

@ -135,3 +135,23 @@ def test_one_shot_delegates_to_model_flow_nous(monkeypatch):
"`hermes portal` must route through _model_flow_nous so the model " "`hermes portal` must route through _model_flow_nous so the model "
"picker runs every time (matching quick setup)." "picker runs every time (matching quick setup)."
) )
@pytest.mark.parametrize("exc", [KeyboardInterrupt, EOFError, SystemExit])
def test_one_shot_swallows_cancel_and_systemexit(monkeypatch, exc):
"""A cancel/abort from the delegated Nous flow must NOT escape and kill the
CLI. `_login_nous` raises SystemExit(130)/(1) on cancel/failure, and the
expired-session re-login path inside `_model_flow_nous` only catches
Exception — so SystemExit could otherwise propagate out. The portal handler
must treat KeyboardInterrupt/EOFError/SystemExit as a graceful cancel.
"""
import hermes_cli.setup as setup_mod
def boom(config):
raise exc
monkeypatch.setattr("hermes_cli.main._model_flow_nous", boom)
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {})
# Must return normally (None), not propagate the exception.
assert setup_mod._run_portal_one_shot({}) is None

View File

@ -95,7 +95,7 @@ You should see Hermes call `web_search` (Firecrawl-backed, through the gateway)
## 5. Pick the model you actually want ## 5. Pick the model you actually want
The default after `hermes setup --portal` is a sensible general-purpose model, but the whole point of the subscription is access to the full catalog. Switch with `/model` mid-session: `hermes setup --portal` lets you pick a model during setup, but the whole point of the subscription is access to the full catalog — switch any time with `/model` mid-session:
```bash ```bash
/model anthropic/claude-sonnet-4.6 # best general-purpose agentic /model anthropic/claude-sonnet-4.6 # best general-purpose agentic

View File

@ -14,7 +14,7 @@ If you only have time to set up one thing, set up this. The fastest path:
hermes setup --portal hermes setup --portal
``` ```
That single command runs the Portal OAuth, sets Nous as your inference provider in `config.yaml`, and turns on the Tool Gateway. You're ready to `hermes chat` immediately after. That single command runs the Portal OAuth, lets you pick a Nous model, sets Nous as your inference provider in `config.yaml`, and turns on the Tool Gateway. You're ready to `hermes chat` immediately after.
Don't have a subscription yet? [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) — sign up, then come back and run the command above. Don't have a subscription yet? [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) — sign up, then come back and run the command above.
@ -99,9 +99,10 @@ This runs the full setup in one shot:
1. Opens your browser to portal.nousresearch.com for OAuth login 1. Opens your browser to portal.nousresearch.com for OAuth login
2. Stores the refresh token at `~/.hermes/auth.json` 2. Stores the refresh token at `~/.hermes/auth.json`
3. Sets Nous as your inference provider in `~/.hermes/config.yaml` 3. Lets you pick a Nous model from the curated list (or skip to keep your current one)
4. Turns on the Tool Gateway (web, image, TTS, browser routing) 4. Sets Nous as your inference provider in `~/.hermes/config.yaml` (when you pick a model)
5. Returns you to your terminal ready to `hermes chat` 5. Turns on the Tool Gateway (web, image, TTS, browser routing)
6. Returns you to your terminal ready to `hermes chat`
If you don't have a subscription yet, sign up at [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) first. If you don't have a subscription yet, sign up at [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) first.

View File

@ -95,7 +95,7 @@ Hey, search the web for "Hermes Agent release notes" and summarize the top 3 hit
## 5. 选择你实际需要的模型 ## 5. 选择你实际需要的模型
`hermes setup --portal` 后的默认模型是一个合理的通用模型,但订阅的意义在于可以访问完整的模型目录在会话中使用 `/model` 切换: `hermes setup --portal` 会在设置过程中让你选择模型,但订阅的意义在于可以访问完整的模型目录——随时可在会话中使用 `/model` 切换:
```bash ```bash
/model anthropic/claude-sonnet-4.6 # 最佳通用 agentic 模型 /model anthropic/claude-sonnet-4.6 # 最佳通用 agentic 模型

View File

@ -14,7 +14,7 @@ description: "一个订阅300+ 前沿模型Tool Gateway以及 Nous Chat
hermes setup --portal hermes setup --portal
``` ```
这条命令会完成 Portal OAuth 认证,在 `config.yaml` 中将 Nous 设为推理提供商,并开启 Tool Gateway。完成后即可立即运行 `hermes chat` 这条命令会完成 Portal OAuth 认证,让你选择一个 Nous 模型,`config.yaml` 中将 Nous 设为推理提供商,并开启 Tool Gateway。完成后即可立即运行 `hermes chat`
还没有订阅?前往 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册,然后回来运行上面的命令。 还没有订阅?前往 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册,然后回来运行上面的命令。
@ -95,9 +95,10 @@ hermes setup --portal
1. 打开浏览器跳转至 portal.nousresearch.com 进行 OAuth 登录 1. 打开浏览器跳转至 portal.nousresearch.com 进行 OAuth 登录
2. 将 refresh token 存储至 `~/.hermes/auth.json` 2. 将 refresh token 存储至 `~/.hermes/auth.json`
3. `~/.hermes/config.yaml` 中将 Nous 设为推理提供商 3. 让你从精选列表中选择一个 Nous 模型(也可跳过以保留当前模型)
4. 开启 Tool Gateway网页、图像、TTS、浏览器路由 4. `~/.hermes/config.yaml` 中将 Nous 设为推理提供商(当你选择模型时
5. 返回终端,即可运行 `hermes chat` 5. 开启 Tool Gateway网页、图像、TTS、浏览器路由
6. 返回终端,即可运行 `hermes chat`
如果还没有订阅,请先在 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册。 如果还没有订阅,请先在 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册。