refactor(hermes_cli): fold verify-token branches, share track/untrack counter, compact probe script

This commit is contained in:
Teknium
2026-09-02 20:59:41 -07:00
parent 46997ee7ed
commit 1f52e93565
5 changed files with 41 additions and 58 deletions

View File

@@ -88,9 +88,8 @@ def _xai_oauth_logged_in_for_setup() -> bool:
def _run_xai_oauth_login_from_setup() -> bool:
"""Run the xAI Grok OAuth device-code login from inside the setup wizard.
Saves OAuth tokens only — does **not** switch the active provider or rewrite
``model.provider`` (callers only need credentials for side tools). Returns True on success,
False on any failure (the caller falls back, e.g. to Edge TTS).
Saves OAuth tokens only — does **not** switch the active provider or rewrite ``model.provider``
(callers only need credentials for side tools). False on any failure (caller falls back).
"""
from hermes_cli.setup import _info, print_warning
try:

View File

@@ -312,24 +312,21 @@ def run_whatsapp_cloud_setup() -> int:
print()
_header("STEP 5 — Verify Token (auto-generated)")
current_verify = get_env_value("WHATSAPP_CLOUD_VERIFY_TOKEN") or None
if current_verify:
print(f" An existing verify token is already set ({current_verify[:8]}...).")
verify_token = get_env_value("WHATSAPP_CLOUD_VERIFY_TOKEN") or None
regen = "y"
if verify_token:
print(f" An existing verify token is already set ({verify_token[:8]}...).")
try:
regen = input(" Generate a new one? [y/N]: ").strip().lower()
except (EOFError, KeyboardInterrupt):
regen = "n"
if regen in {"y", "yes"}:
verify_token = secrets.token_urlsafe(32)
save_env_value("WHATSAPP_CLOUD_VERIFY_TOKEN", verify_token)
print(f" ✓ New verify token: {verify_token}")
else:
verify_token = current_verify
print(" ✓ Keeping existing verify token")
else:
if regen in {"y", "yes"}:
label = "New verify token" if verify_token else "Generated"
verify_token = secrets.token_urlsafe(32)
save_env_value("WHATSAPP_CLOUD_VERIFY_TOKEN", verify_token)
print(f" ✓ Generated: {verify_token}")
print(f" ✓ {label}: {verify_token}")
else:
print(" ✓ Keeping existing verify token")
_lines("", " → COPY THIS TOKEN NOW. You'll paste it into Meta's webhook",
" configuration dialog (next step).", "")

View File

@@ -47,21 +47,15 @@ class SQLiteRuntimeInfo:
_PROBE_SCRIPT = """
import json
import sqlite3
import sys
import json, sqlite3, sys
conn = sqlite3.connect(":memory:")
try:
row = conn.execute("SELECT sqlite_source_id()").fetchone()
finally:
conn.close()
print(json.dumps({
"base_prefix": sys.base_prefix,
"executable": sys.executable,
"python_version": list(sys.version_info[:3]),
"sqlite_version": list(sqlite3.sqlite_version_info),
"base_prefix": sys.base_prefix, "executable": sys.executable,
"python_version": list(sys.version_info[:3]), "sqlite_version": list(sqlite3.sqlite_version_info),
"sqlite_version_string": sqlite3.sqlite_version,
"sqlite_source_id": str(row[0]) if row and row[0] is not None else "",
}))
@@ -91,8 +85,7 @@ def probe_sqlite_runtime(python: str | Path, *, timeout: float = 30.0) -> SQLite
try:
payload = json.loads(result.stdout)
return SQLiteRuntimeInfo(
executable=Path(str(payload["executable"])),
base_prefix=Path(str(payload["base_prefix"])),
executable=Path(str(payload["executable"])), base_prefix=Path(str(payload["base_prefix"])),
python_version=_version_tuple(payload["python_version"]),
sqlite_version=_version_tuple(payload["sqlite_version"]),
sqlite_version_string=str(payload["sqlite_version_string"]),

View File

@@ -68,20 +68,19 @@ def track_connection(path: Path | str) -> None:
_track_key(_key(path))
def _track_key(key: str) -> None:
"""Bump the live count for an already-canonical key (caller holds ``_live_lock``)."""
_live_connections[key] = _live_connections.get(key, 0) + 1
def _track_key(key: str, delta: int = 1) -> None:
"""Adjust the live count for an already-canonical key (caller holds ``_live_lock``)."""
remaining = _live_connections.get(key, 0) + delta
if remaining > 0:
_live_connections[key] = remaining
else:
_live_connections.pop(key, None)
def untrack_connection(path: Path | str) -> None:
"""Record that one connection to *path* has been closed."""
key = _key(path)
with _live_lock:
remaining = _live_connections.get(key, 0) - 1
if remaining > 0:
_live_connections[key] = remaining
else:
_live_connections.pop(key, None)
_track_key(_key(path), -1)
def has_live_connection(path: Path | str) -> bool:

View File

@@ -1,10 +1,7 @@
"""Credential sections of `hermes status`: API keys, OAuth providers, Nous Tool Gateway,
API-key providers. Split out of ``hermes_cli/status.py``; the renderers are re-imported there
and run through ``status._SECTIONS`` with the shared ``_StatusContext``.
Origin helpers (``_row``, ``_first_env_value``, ...) are imported lazily from
``hermes_cli.status`` so tests that monkeypatch that module keep working.
"""
"""Credential sections of `hermes status` (API keys, OAuth providers, Nous Tool Gateway, API-key
providers), run through ``status._SECTIONS`` with the shared ``_StatusContext``. Origin helpers
(``_row``, ``_first_env_value``, ...) are imported lazily from ``hermes_cli.status`` so tests that
monkeypatch that module keep working."""
from hermes_cli.auth import AuthError
from hermes_cli.nous_account import (
@@ -23,7 +20,9 @@ def _format_iso_timestamp(value) -> str:
parsed = datetime.fromisoformat(text[:-1] + "+00:00" if text.endswith("Z") else text)
except Exception:
return value
return (parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=timezone.utc)).astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
def _qwen_expiry(expires_at_ms) -> str:
@@ -117,14 +116,13 @@ def _render_auth_providers(ctx):
_section("Auth Providers")
import hermes_cli.auth as auth
try:
# Read-only display: use the refresh-free snapshot so `hermes status`
# never performs an OAuth refresh or burns a single-use refresh token.
# Read-only display: the refresh-free snapshot, so `hermes status` never performs an OAuth
# refresh or burns a single-use refresh token.
nous_status = auth.get_nous_auth_status_local()
statuses = {getter: getattr(auth, getter)() for _, getter, _, _ in _OAUTH_BLOCKS[:3]}
except Exception:
nous_status, statuses = {}, {}
# xAI OAuth is guarded separately so an import failure there cannot disrupt
# the Nous/Codex/Qwen/MiniMax rows.
# xAI OAuth is guarded separately so an import failure there cannot disrupt the other rows.
try:
statuses["get_xai_oauth_auth_status"] = auth.get_xai_oauth_auth_status() or {}
except Exception:
@@ -136,18 +134,16 @@ def _render_auth_providers(ctx):
try:
info = get_nous_portal_account_info()
except Exception:
info = None
pass
ctx.nous_account_info = info
ctx.nous_logged_in = logged_in = bool(nous_status.get("logged_in") or (info and info.logged_in))
ctx.nous_inference_present = inference = bool(
nous_status.get("inference_credential_present") or (info and info.inference_credential_present)
)
nous_error = nous_status.get("error")
_row(
"Nous Portal", logged_in,
"logged in" if logged_in
else "not logged in (Nous inference key configured)" if inference
else "not logged in (run: hermes portal)")
_row("Nous Portal", logged_in,
"logged in" if logged_in else "not logged in (Nous inference key configured)" if inference
else "not logged in (run: hermes portal)")
portal_url = nous_status.get("portal_base_url") or "(unknown)"
inference_url = nous_status.get("inference_base_url") or (info.inference_base_url if info else None)
for label, value, show in (
@@ -185,8 +181,8 @@ def _render_nous_gateway(ctx):
state = "not configured"
print(f" {f.label:<15} {check_mark(f.available or f.active or f.managed_by_nous)} {state}")
elif ctx.nous_logged_in or ctx.nous_inference_present:
# Nous OAuth without entitlement, or an opaque inference key without
# Portal account information, cannot enable the Tool Gateway.
# Nous OAuth without entitlement, or an opaque inference key without Portal account
# information, cannot enable the Tool Gateway.
_section("Nous Tool Gateway")
message = format_nous_portal_entitlement_message(
ctx.nous_account_info, capability="managed web, image, TTS, STT, browser, and Modal tools"
@@ -202,9 +198,8 @@ def _render_apikey_providers(ctx):
configured = bool(_first_env_value(env_vars))
print(f" {pname:<16} {check_mark(configured)} {'configured' if configured else 'not configured (run: hermes model)'}")
# LM Studio reachability: probe only when it is the active provider so users
# with foreign configs see no noise. Auth rejection vs. a silent empty list
# is the most common LM Studio support case.
# LM Studio reachability: probe only when it is the active provider so users with foreign
# configs see no noise. Auth rejection vs. a silent empty list is the common support case.
if _effective_provider_label() == "LM Studio":
from hermes_cli.models import probe_lmstudio_models
model_cfg = ctx.config.get("model")