feat(tools): always show Nous Tool Gateway backends, login on select (#35792)
* feat(tools): always show Nous Tool Gateway backends, login on select The Nous-managed Tool Gateway rows in `hermes tools` (Firecrawl, OpenAI TTS, Browser Use, FAL image/video) were hidden unless the user was already logged into Nous Portal with paid access. Now they are always listed. Selecting one runs an inline Nous Portal device-code OAuth + entitlement check — auth only, no inference-provider switch and no bulk 'enable all tools' prompt (that stays in `hermes model`). The row only activates the gateway once paid access is confirmed. - _visible_providers: stop hiding managed_nous_feature rows (incl. those also flagged requires_nous_auth); pure pre-auth UX rows still gate on login - nous_subscription.ensure_nous_portal_access(): auth + entitlement gate that preserves the user's active inference provider - _configure_provider / _reconfigure_provider: run the inline gate for managed backends; write config only when entitled - picker marker: 'via Nous Portal (login on select)' for logged-out users - _hidden_nous_gateway_message: now a no-op (rows are never hidden) * docs: hermes tools is a first-class Tool Gateway entry point The Tool Gateway docs framed `hermes setup --portal` / `hermes model` as the activation path and only mentioned `hermes tools` for mixing in your own keys. With the inline-login change, picking a Nous-managed backend in `hermes tools` is a complete path on its own — it logs you into Nous Portal on select if needed, without switching your inference provider or prompting to enable every other tool. - tool-gateway.md: Get started now lists three peer entry points; new paragraph explaining login-on-select and the no-prompt fast path when OAuth is already active - nous-portal.md + run-hermes-with-nous-portal.md: note that managed rows appear logged-out and trigger inline login on select
This commit is contained in:
+73
-34
@@ -1876,18 +1876,26 @@ def _visible_providers(
|
||||
*,
|
||||
force_fresh: bool = False,
|
||||
) -> list[dict]:
|
||||
"""Return provider entries visible for the current auth/config state."""
|
||||
"""Return provider entries visible for the current auth/config state.
|
||||
|
||||
Nous-managed Tool Gateway rows (``managed_nous_feature``) are always
|
||||
shown — even to logged-out / unentitled users — so the picker advertises
|
||||
that the capability exists. Selecting one drives an inline Nous Portal
|
||||
login + entitlement check (see ``_configure_provider``); the row only
|
||||
*activates* the gateway once paid access is confirmed.
|
||||
"""
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
managed_available = bool(
|
||||
features.account_info
|
||||
and features.account_info.logged_in
|
||||
and features.account_info.paid_service_access is True
|
||||
)
|
||||
visible = []
|
||||
for provider in cat.get("providers", []):
|
||||
if provider.get("managed_nous_feature") and not managed_available:
|
||||
continue
|
||||
if provider.get("requires_nous_auth") and not features.nous_auth_present:
|
||||
# Nous-managed Tool Gateway rows stay visible regardless of auth —
|
||||
# selecting one drives an inline Portal login. A `requires_nous_auth`
|
||||
# row that is NOT a managed gateway feature (pure pre-auth UX) is
|
||||
# still hidden until the user is logged in.
|
||||
if (
|
||||
provider.get("requires_nous_auth")
|
||||
and not provider.get("managed_nous_feature")
|
||||
and not features.nous_auth_present
|
||||
):
|
||||
continue
|
||||
visible.append(provider)
|
||||
|
||||
@@ -1933,22 +1941,16 @@ def _hidden_nous_gateway_message(
|
||||
*,
|
||||
force_fresh: bool = False,
|
||||
) -> str:
|
||||
"""Return a reason when a category's Nous provider is hidden."""
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
managed_available = bool(
|
||||
features.account_info
|
||||
and features.account_info.logged_in
|
||||
and features.account_info.paid_service_access is True
|
||||
)
|
||||
if managed_available:
|
||||
return ""
|
||||
if not any(p.get("managed_nous_feature") for p in cat.get("providers", [])):
|
||||
return ""
|
||||
message = format_nous_portal_entitlement_message(
|
||||
features.account_info,
|
||||
capability=capability,
|
||||
)
|
||||
return message or ""
|
||||
"""Deprecated: Nous Tool Gateway rows are no longer hidden.
|
||||
|
||||
Previously this returned a "log in / upgrade" banner shown above a
|
||||
category when its Nous-managed rows were filtered out for unentitled
|
||||
users. Those rows are now always listed (see ``_visible_providers``), and
|
||||
the login + entitlement guidance happens inline when the user selects one
|
||||
(``ensure_nous_portal_access``). Kept as a no-op so call sites stay simple;
|
||||
always returns an empty string.
|
||||
"""
|
||||
return ""
|
||||
|
||||
|
||||
_POST_SETUP_INSTALLED: dict = {
|
||||
@@ -2132,14 +2134,17 @@ def _configure_tool_category(
|
||||
configured = ""
|
||||
else:
|
||||
configured = " [configured]"
|
||||
# Highlight Nous-managed entries when the user has Portal auth.
|
||||
# curses_radiolist can't render ANSI inside item strings, so we
|
||||
# use a plain unicode star + parenthetical phrase. Suppressed
|
||||
# when no Portal auth is present so non-subscribers see the
|
||||
# picker unchanged.
|
||||
# Mark Nous-managed entries. Logged-in paid subscribers get the
|
||||
# "included" star; everyone else gets a "via Nous Portal" hint so
|
||||
# it's clear selecting the row triggers a Portal login. The rows
|
||||
# are always shown now (see _visible_providers) — selecting one
|
||||
# drives an inline login + entitlement check.
|
||||
sub_marker = ""
|
||||
if _nous_logged_in and p.get("managed_nous_feature"):
|
||||
sub_marker = " ★ Included with your Nous subscription"
|
||||
if p.get("managed_nous_feature"):
|
||||
if _nous_logged_in:
|
||||
sub_marker = " ★ Included with your Nous subscription"
|
||||
else:
|
||||
sub_marker = " ★ via Nous Portal (login on select)"
|
||||
provider_choices.append(f"{p['name']}{badge}{tag}{configured}{sub_marker}")
|
||||
|
||||
# Add skip option
|
||||
@@ -2558,7 +2563,26 @@ def _configure_provider(
|
||||
env_vars = provider.get("env_vars", [])
|
||||
managed_feature = provider.get("managed_nous_feature")
|
||||
|
||||
if provider.get("requires_nous_auth"):
|
||||
# Nous-managed Tool Gateway backends are always listed (see
|
||||
# _visible_providers), but only *activate* once the user has paid Nous
|
||||
# Portal access. Selecting one runs an inline Portal login when needed —
|
||||
# auth + entitlement only, no inference-provider switch and no bulk
|
||||
# "enable all tools" prompt (that lives in `hermes model`).
|
||||
if managed_feature:
|
||||
from hermes_cli.nous_subscription import ensure_nous_portal_access
|
||||
|
||||
if not ensure_nous_portal_access(
|
||||
capability=f"{provider.get('name', 'the Nous Tool Gateway')}"
|
||||
):
|
||||
_print_warning(
|
||||
" Not enabled — Nous Portal paid access is required for this backend."
|
||||
)
|
||||
return
|
||||
|
||||
# Pure pre-auth UX rows (requires_nous_auth without a managed gateway
|
||||
# feature) keep the old gate. Managed rows are handled by the inline
|
||||
# login above, so don't double-check them here.
|
||||
if provider.get("requires_nous_auth") and not managed_feature:
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
entitled = bool(
|
||||
features.account_info and features.account_info.paid_service_access is True
|
||||
@@ -2922,7 +2946,22 @@ def _reconfigure_provider(
|
||||
env_vars = provider.get("env_vars", [])
|
||||
managed_feature = provider.get("managed_nous_feature")
|
||||
|
||||
if provider.get("requires_nous_auth"):
|
||||
# Same inline Nous Portal login + entitlement gate as _configure_provider:
|
||||
# managed Tool Gateway backends only activate with paid Portal access.
|
||||
if managed_feature:
|
||||
from hermes_cli.nous_subscription import ensure_nous_portal_access
|
||||
|
||||
if not ensure_nous_portal_access(
|
||||
capability=f"{provider.get('name', 'the Nous Tool Gateway')}"
|
||||
):
|
||||
_print_warning(
|
||||
" Not enabled — Nous Portal paid access is required for this backend."
|
||||
)
|
||||
return
|
||||
|
||||
# Pure pre-auth UX rows keep the old gate; managed rows already handled
|
||||
# by the inline login above.
|
||||
if provider.get("requires_nous_auth") and not managed_feature:
|
||||
features = get_nous_subscription_features(config, force_fresh=force_fresh)
|
||||
entitled = bool(
|
||||
features.account_info and features.account_info.paid_service_access is True
|
||||
|
||||
Reference in New Issue
Block a user