feat(cli): make hermes portal the human-readable Portal onboarding alias

`hermes portal` (no subcommand) now runs the one-shot Nous Portal onboarding
— OAuth login, switch provider to Nous, offer Tool Gateway — identical to
`hermes setup --portal` and the human-readable alias for
`hermes auth add nous --type oauth` (which still works).

The prior status default moves to `hermes portal info`; `status` is kept as a
hidden back-compat alias. `open`/`tools` subcommands are unchanged.

User-facing hints and docs (status.py, conversation_loop 401 guidance,
SystemPage, README, website docs + zh-Hans) now point at `hermes portal` /
`hermes portal info`. `--manual-paste` references keep the explicit auth
command since `hermes portal` does not expose that flag.
This commit is contained in:
kshitijk4poor 2026-06-04 01:19:28 +05:30
parent 39fee4f3bc
commit da4f407e51
21 changed files with 212 additions and 58 deletions

View file

@ -1,12 +1,20 @@
"""``hermes portal`` — small CLI surface for Nous Portal users.
"""``hermes portal`` — the human-readable entry point for Nous Portal.
Running ``hermes portal`` with no subcommand performs the one-shot Portal
onboarding: OAuth login, switch the inference provider to Nous, and offer to
enable the Tool Gateway. It is the friendly alias for
``hermes auth add nous --type oauth`` (which still works) and is identical to
``hermes setup --portal``.
Subcommands:
status Show Portal auth state + which Tool Gateway tools are routed.
(none) Log in to Nous Portal + set it up (one-shot onboarding).
login Explicit alias for the default one-shot onboarding.
info Show Portal auth state + which Tool Gateway tools are routed.
open Open the Portal subscription page in the user's default browser.
tools List Tool Gateway tools and which are active in the current config.
This command is intentionally minimal it does not duplicate functionality
already in ``hermes auth`` or ``hermes tools``. It's a discovery + status
already in ``hermes auth`` or ``hermes tools``. It's the onboarding + discovery
surface for the Portal subscription itself.
"""
from __future__ import annotations
@ -49,7 +57,7 @@ def _cmd_status(args) -> int:
else:
print(f" Auth: {color('not logged in', Colors.YELLOW)}")
print(f" Sign up: {SUBSCRIPTION_URL}")
print(f" Login: hermes auth add nous --type oauth")
print(f" Login: hermes portal")
# Provider selection (independent of auth)
model_cfg = config.get("model") if isinstance(config.get("model"), dict) else {}
@ -134,7 +142,7 @@ def _cmd_tools(args) -> int:
print(color(" ────────────────────", Colors.MAGENTA))
if not features.nous_auth_present:
print(color(" Not logged into Nous Portal — sign in with `hermes auth add nous --type oauth`.", Colors.YELLOW))
print(color(" Not logged into Nous Portal — sign in with `hermes portal`.", Colors.YELLOW))
print()
label_width = max(len(label) for _, label, _ in catalog)
@ -158,14 +166,36 @@ def _cmd_tools(args) -> int:
return 0
def _cmd_login(args) -> int:
"""Run the one-shot Nous Portal onboarding (OAuth + provider + Tool Gateway).
This is the human-readable front door for `hermes auth add nous --type
oauth`. It reuses the exact wiring behind `hermes setup --portal` so the
two commands stay in lockstep: device-code login, switch the inference
provider to Nous, then offer the Tool Gateway opt-in.
"""
from hermes_cli.setup import _run_portal_one_shot
config = load_config() or {}
try:
_run_portal_one_shot(config)
except (KeyboardInterrupt, EOFError):
print()
print("Portal setup cancelled.")
return 1
return 0
def portal_command(args) -> int:
"""Top-level dispatch for `hermes portal <subcommand>`."""
sub = getattr(args, "portal_command", None)
if sub in {None, ""}:
# Default to status — matches gh / kubectl conventions where the
# subcommand-less form gives a useful overview.
return _cmd_status(args)
if sub == "status":
if sub in {None, "", "login"}:
# Default to the one-shot onboarding — `hermes portal` is the
# human-readable alias for `hermes auth add nous --type oauth` /
# `hermes setup --portal`.
return _cmd_login(args)
if sub in {"info", "status"}:
# `status` kept as a back-compat alias for the prior default.
return _cmd_status(args)
if sub == "open":
return _cmd_open(args)
@ -180,19 +210,26 @@ def add_parser(subparsers) -> None:
"""Register `hermes portal` on the given argparse subparsers object."""
portal_parser = subparsers.add_parser(
"portal",
help="Nous Portal status, subscription, and Tool Gateway routing",
help="Set up Nous Portal (OAuth login + Tool Gateway); see also `portal info`",
description=(
"Inspect Nous Portal auth, Tool Gateway routing, and open the "
"Portal subscription page. Subcommands: status (default), "
"open, tools."
"Run `hermes portal` with no subcommand to log in to Nous Portal "
"and set it up (the human-readable alias for `hermes auth add nous "
"--type oauth`, identical to `hermes setup --portal`). Subcommands: "
"login (default), info, open, tools."
),
)
portal_sub = portal_parser.add_subparsers(dest="portal_command")
portal_sub.add_parser(
"status",
help="Show Portal auth + Tool Gateway routing summary (default)",
"login",
help="Log in to Nous Portal + set it up (default; one-shot onboarding)",
)
portal_sub.add_parser(
"info",
help="Show Portal auth + Tool Gateway routing summary",
)
# `status` retained as a hidden back-compat alias for `info`.
portal_sub.add_parser("status")
portal_sub.add_parser(
"open",
help="Open the Portal subscription page in your default browser",

View file

@ -2828,7 +2828,7 @@ def _run_portal_one_shot(config: dict) -> None:
print()
print_success("Portal setup complete.")
print_info(" Run `hermes portal status` to inspect routing.")
print_info(" Run `hermes portal info` to inspect routing.")
print_info(" Run `hermes` to start chatting.")

View file

@ -223,7 +223,7 @@ def show_status(args):
elif nous_inference_present:
nous_label = "not logged in (Nous inference key configured)"
else:
nous_label = "not logged in (run: hermes auth add nous --type oauth)"
nous_label = "not logged in (run: hermes portal)"
print(
f" {'Nous Portal':<12} {check_mark(nous_logged_in)} "
f"{nous_label}"