fix(dashboard): honor --portal-url / HERMES_DASHBOARD_PORTAL_URL override in register

The register command resolved the portal base URL purely from the stored
login, ignoring any override. That meant `HERMES_DASHBOARD_PORTAL_URL` (and
the absence of any flag) gave no way to point registration at a staging or
preview portal — the request always hit the login's portal, returning 404
against a branch that wasn't deployed there.

- _resolve_portal_base_url now takes an optional override (precedence:
  override > stored login portal > prod default).
- New --portal-url flag; falls back to HERMES_DASHBOARD_PORTAL_URL env.
- Documents that the access token must be valid at the overridden portal
  (it's minted by whoever you logged into).
- 3 new tests for override precedence.

Verified live against the PR #324 Vercel preview: CLI -> preview endpoint ->
real agent:{id} client_id written to .env.
This commit is contained in:
Ben
2026-06-04 16:28:27 +10:00
committed by Teknium
parent bb291b6bbc
commit c2ca3f01ab
3 changed files with 62 additions and 3 deletions

View File

@ -25,6 +25,7 @@ so this client never needs to know the namespace convention.
from __future__ import annotations
import json
import os
import random
import sys
import urllib.error
@ -61,8 +62,22 @@ def _generate_dashboard_name() -> str:
return f"{random.choice(_NAME_ADJECTIVES)}_{random.choice(_NAME_NOUNS)}"
def _resolve_portal_base_url() -> str:
"""Best-effort portal base URL from the stored Nous login, with default."""
def _resolve_portal_base_url(override: Optional[str] = None) -> str:
"""Resolve the portal base URL for the registration request.
Precedence:
1. ``override`` — explicit ``--portal-url`` flag or
``HERMES_DASHBOARD_PORTAL_URL`` env (used for testing against a
preview/staging portal). NOTE: the access token must be valid at
this portal — it's minted by whatever portal you logged into, so an
override only works if the token's issuer matches (e.g. you logged
into the same staging/preview portal).
2. The ``portal_base_url`` stored on the Nous login — this is the
portal that issued the token, so it's the correct default target.
3. The production default.
"""
if isinstance(override, str) and override.strip():
return override.rstrip("/")
try:
from hermes_cli.auth import DEFAULT_NOUS_PORTAL_URL, get_provider_auth_state
@ -223,7 +238,12 @@ def cmd_dashboard_register(args) -> None:
print(f"✗ Could not resolve a Nous Portal access token: {exc}")
sys.exit(1)
portal_base_url = _resolve_portal_base_url()
# Portal override: explicit --portal-url flag wins, else the
# HERMES_DASHBOARD_PORTAL_URL env var, else the stored login's portal.
portal_override = getattr(args, "portal_url", None) or os.environ.get(
"HERMES_DASHBOARD_PORTAL_URL"
)
portal_base_url = _resolve_portal_base_url(portal_override)
name = getattr(args, "name", None) or _generate_dashboard_name()
custom_redirect_uri = getattr(args, "redirect_uri", None)

View File

@ -15326,6 +15326,17 @@ Examples:
"https://hermes.example.com/auth/callback. Omit for localhost-only use."
),
)
dashboard_register_parser.add_argument(
"--portal-url",
dest="portal_url",
default=None,
help=(
"Override the Nous Portal base URL for registration (default: the "
"portal you logged into). The access token must be valid at this "
"portal. Also settable via HERMES_DASHBOARD_PORTAL_URL. Mainly for "
"testing against a staging/preview portal."
),
)
dashboard_register_parser.set_defaults(func=cmd_dashboard_register)
# =========================================================================

View File

@ -157,6 +157,34 @@ class TestHappyPath:
)
class TestPortalResolution:
def test_override_arg_wins(self):
assert (
dr._resolve_portal_base_url("https://preview.example.com/")
== "https://preview.example.com"
)
def test_falls_back_to_stored_login_portal(self):
with patch(
"hermes_cli.auth.get_provider_auth_state",
return_value={"portal_base_url": "https://portal.staging-nousresearch.com"},
):
assert (
dr._resolve_portal_base_url(None)
== "https://portal.staging-nousresearch.com"
)
def test_blank_override_ignored(self):
with patch(
"hermes_cli.auth.get_provider_auth_state",
return_value={"portal_base_url": "https://portal.staging-nousresearch.com"},
):
assert (
dr._resolve_portal_base_url(" ")
== "https://portal.staging-nousresearch.com"
)
class TestPortalErrors:
def _run_http_error(self, code, body):
err = urllib.error.HTTPError(