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:
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
@ -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(
|
||||
|
||||
Reference in New Issue
Block a user