refactor(hermes_cli): fold verify-token branches, share track/untrack counter, compact probe script
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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).", "")
|
||||
|
||||
|
||||
@@ -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"]),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user