2026-02-02 19:01:51 -08:00
|
|
|
"""
|
|
|
|
|
Status command for hermes CLI.
|
|
|
|
|
|
|
|
|
|
Shows the status of all Hermes Agent components.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import subprocess
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
|
|
|
|
|
2026-02-20 23:23:32 -08:00
|
|
|
from hermes_cli.colors import Colors, color
|
2026-02-26 16:49:14 +11:00
|
|
|
from hermes_cli.config import get_env_path, get_env_value
|
2026-02-20 23:23:32 -08:00
|
|
|
from hermes_constants import OPENROUTER_MODELS_URL
|
2026-02-02 19:01:51 -08:00
|
|
|
|
|
|
|
|
def check_mark(ok: bool) -> str:
|
|
|
|
|
if ok:
|
|
|
|
|
return color("✓", Colors.GREEN)
|
|
|
|
|
return color("✗", Colors.RED)
|
|
|
|
|
|
|
|
|
|
def redact_key(key: str) -> str:
|
|
|
|
|
"""Redact an API key for display."""
|
|
|
|
|
if not key:
|
|
|
|
|
return "(not set)"
|
|
|
|
|
if len(key) < 12:
|
|
|
|
|
return "***"
|
|
|
|
|
return key[:4] + "..." + key[-4:]
|
|
|
|
|
|
|
|
|
|
|
2026-02-20 17:24:00 -08:00
|
|
|
def _format_iso_timestamp(value) -> str:
|
|
|
|
|
"""Format ISO timestamps for status output, converting to local timezone."""
|
|
|
|
|
if not value or not isinstance(value, str):
|
|
|
|
|
return "(unknown)"
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
text = value.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
return "(unknown)"
|
|
|
|
|
if text.endswith("Z"):
|
|
|
|
|
text = text[:-1] + "+00:00"
|
|
|
|
|
try:
|
|
|
|
|
parsed = datetime.fromisoformat(text)
|
|
|
|
|
if parsed.tzinfo is None:
|
|
|
|
|
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
|
|
|
except Exception:
|
|
|
|
|
return value
|
|
|
|
|
return parsed.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 19:01:51 -08:00
|
|
|
def show_status(args):
|
|
|
|
|
"""Show status of all Hermes Agent components."""
|
|
|
|
|
show_all = getattr(args, 'all', False)
|
|
|
|
|
deep = getattr(args, 'deep', False)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print(color("┌─────────────────────────────────────────────────────────┐", Colors.CYAN))
|
2026-02-20 21:25:04 -08:00
|
|
|
print(color("│ ⚕ Hermes Agent Status │", Colors.CYAN))
|
2026-02-02 19:01:51 -08:00
|
|
|
print(color("└─────────────────────────────────────────────────────────┘", Colors.CYAN))
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Environment
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Environment", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
print(f" Project: {PROJECT_ROOT}")
|
|
|
|
|
print(f" Python: {sys.version.split()[0]}")
|
|
|
|
|
|
2026-02-26 16:49:14 +11:00
|
|
|
env_path = get_env_path()
|
2026-02-02 19:01:51 -08:00
|
|
|
print(f" .env file: {check_mark(env_path.exists())} {'exists' if env_path.exists() else 'not found'}")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# API Keys
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ API Keys", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
|
"OpenRouter": "OPENROUTER_API_KEY",
|
|
|
|
|
"Anthropic": "ANTHROPIC_API_KEY",
|
|
|
|
|
"OpenAI": "OPENAI_API_KEY",
|
feat: add z.ai/GLM, Kimi/Moonshot, MiniMax as first-class providers
Adds 4 new direct API-key providers (zai, kimi-coding, minimax, minimax-cn)
to the inference provider system. All use standard OpenAI-compatible
chat/completions endpoints with Bearer token auth.
Core changes:
- auth.py: Extended ProviderConfig with api_key_env_vars and base_url_env_var
fields. Added providers to PROVIDER_REGISTRY. Added provider aliases
(glm, z-ai, zhipu, kimi, moonshot). Added auto-detection of API-key
providers in resolve_provider(). Added resolve_api_key_provider_credentials()
and get_api_key_provider_status() helpers.
- runtime_provider.py: Added generic API-key provider branch in
resolve_runtime_provider() — any provider with auth_type='api_key'
is automatically handled.
- main.py: Added providers to hermes model menu with generic
_model_flow_api_key_provider() flow. Updated _has_any_provider_configured()
to check all provider env vars. Updated argparse --provider choices.
- setup.py: Added providers to setup wizard with API key prompts and
curated model lists.
- config.py: Added env vars (GLM_API_KEY, KIMI_API_KEY, MINIMAX_API_KEY,
etc.) to OPTIONAL_ENV_VARS.
- status.py: Added API key display and provider status section.
- doctor.py: Added connectivity checks for each provider endpoint.
- cli.py: Updated provider docstrings.
Docs: Updated README.md, .env.example, cli-config.yaml.example,
cli-commands.md, environment-variables.md, configuration.md.
Tests: 50 new tests covering registry, aliases, resolution, auto-detection,
credential resolution, and runtime provider dispatch.
Inspired by PR #33 (numman-ali) which proposed a provider registry approach.
Credit to tars90percent (PR #473) and manuelschipper (PR #420) for related
provider improvements merged earlier in this changeset.
2026-03-06 18:55:12 -08:00
|
|
|
"Z.AI/GLM": "GLM_API_KEY",
|
|
|
|
|
"Kimi": "KIMI_API_KEY",
|
|
|
|
|
"MiniMax": "MINIMAX_API_KEY",
|
|
|
|
|
"MiniMax-CN": "MINIMAX_CN_API_KEY",
|
2026-02-02 19:01:51 -08:00
|
|
|
"Firecrawl": "FIRECRAWL_API_KEY",
|
2026-03-07 01:23:27 -08:00
|
|
|
"Browserbase": "BROWSERBASE_API_KEY", # Optional — local browser works without this
|
2026-02-02 19:01:51 -08:00
|
|
|
"FAL": "FAL_KEY",
|
2026-02-04 09:36:51 -08:00
|
|
|
"Tinker": "TINKER_API_KEY",
|
|
|
|
|
"WandB": "WANDB_API_KEY",
|
2026-02-12 10:05:08 -08:00
|
|
|
"ElevenLabs": "ELEVENLABS_API_KEY",
|
Add Skills Hub — universal skill search, install, and management from online registries
Implements the Hermes Skills Hub with agentskills.io spec compliance,
multi-registry skill discovery, security scanning, and user-driven
management via CLI and /skills slash command.
Core features:
- Security scanner (tools/skills_guard.py): 120 threat patterns across
12 categories, trust-aware install policy (builtin/trusted/community),
structural checks, unicode injection detection, LLM audit pass
- Hub client (tools/skills_hub.py): GitHub, ClawHub, Claude Code
marketplace, and LobeHub source adapters with shared GitHubAuth
(PAT + gh CLI + GitHub App), lock file provenance tracking, quarantine
flow, and unified search across all sources
- CLI interface (hermes_cli/skills_hub.py): search, install, inspect,
list, audit, uninstall, publish (GitHub PR), snapshot export/import,
and tap management — powers both `hermes skills` and `/skills`
Spec conformance (Phase 0):
- Upgraded frontmatter parser to yaml.safe_load with fallback
- Migrated 39 SKILL.md files: tags/related_skills to metadata.hermes.*
- Added assets/ directory support and compatibility/metadata fields
- Excluded .hub/ from skill discovery in skills_tool.py
Updated 13 config/doc files including README, AGENTS.md, .env.example,
setup wizard, doctor, status, pyproject.toml, and docs.
2026-02-18 16:09:05 -08:00
|
|
|
"GitHub": "GITHUB_TOKEN",
|
2026-02-02 19:01:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, env_var in keys.items():
|
2026-02-26 16:49:14 +11:00
|
|
|
value = get_env_value(env_var) or ""
|
2026-02-02 19:01:51 -08:00
|
|
|
has_key = bool(value)
|
|
|
|
|
display = redact_key(value) if not show_all else value
|
|
|
|
|
print(f" {name:<12} {check_mark(has_key)} {display}")
|
2026-02-20 17:24:00 -08:00
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Auth Providers (OAuth)
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Auth Providers", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
try:
|
2026-02-25 18:20:38 -08:00
|
|
|
from hermes_cli.auth import get_nous_auth_status, get_codex_auth_status
|
2026-02-20 17:24:00 -08:00
|
|
|
nous_status = get_nous_auth_status()
|
2026-02-25 18:20:38 -08:00
|
|
|
codex_status = get_codex_auth_status()
|
2026-02-20 17:24:00 -08:00
|
|
|
except Exception:
|
|
|
|
|
nous_status = {}
|
2026-02-25 18:20:38 -08:00
|
|
|
codex_status = {}
|
2026-02-20 17:24:00 -08:00
|
|
|
|
|
|
|
|
nous_logged_in = bool(nous_status.get("logged_in"))
|
|
|
|
|
print(
|
|
|
|
|
f" {'Nous Portal':<12} {check_mark(nous_logged_in)} "
|
2026-02-28 21:47:51 -08:00
|
|
|
f"{'logged in' if nous_logged_in else 'not logged in (run: hermes model)'}"
|
2026-02-20 17:24:00 -08:00
|
|
|
)
|
|
|
|
|
if nous_logged_in:
|
|
|
|
|
portal_url = nous_status.get("portal_base_url") or "(unknown)"
|
|
|
|
|
access_exp = _format_iso_timestamp(nous_status.get("access_expires_at"))
|
|
|
|
|
key_exp = _format_iso_timestamp(nous_status.get("agent_key_expires_at"))
|
|
|
|
|
refresh_label = "yes" if nous_status.get("has_refresh_token") else "no"
|
|
|
|
|
print(f" Portal URL: {portal_url}")
|
|
|
|
|
print(f" Access exp: {access_exp}")
|
|
|
|
|
print(f" Key exp: {key_exp}")
|
|
|
|
|
print(f" Refresh: {refresh_label}")
|
|
|
|
|
|
2026-02-25 18:20:38 -08:00
|
|
|
codex_logged_in = bool(codex_status.get("logged_in"))
|
|
|
|
|
print(
|
|
|
|
|
f" {'OpenAI Codex':<12} {check_mark(codex_logged_in)} "
|
2026-02-28 21:47:51 -08:00
|
|
|
f"{'logged in' if codex_logged_in else 'not logged in (run: hermes model)'}"
|
2026-02-25 18:20:38 -08:00
|
|
|
)
|
2026-03-05 21:27:12 +03:00
|
|
|
codex_auth_file = codex_status.get("auth_store")
|
2026-02-25 18:20:38 -08:00
|
|
|
if codex_auth_file:
|
|
|
|
|
print(f" Auth file: {codex_auth_file}")
|
|
|
|
|
codex_last_refresh = _format_iso_timestamp(codex_status.get("last_refresh"))
|
|
|
|
|
if codex_status.get("last_refresh"):
|
|
|
|
|
print(f" Refreshed: {codex_last_refresh}")
|
|
|
|
|
if codex_status.get("error") and not codex_logged_in:
|
|
|
|
|
print(f" Error: {codex_status.get('error')}")
|
|
|
|
|
|
feat: add z.ai/GLM, Kimi/Moonshot, MiniMax as first-class providers
Adds 4 new direct API-key providers (zai, kimi-coding, minimax, minimax-cn)
to the inference provider system. All use standard OpenAI-compatible
chat/completions endpoints with Bearer token auth.
Core changes:
- auth.py: Extended ProviderConfig with api_key_env_vars and base_url_env_var
fields. Added providers to PROVIDER_REGISTRY. Added provider aliases
(glm, z-ai, zhipu, kimi, moonshot). Added auto-detection of API-key
providers in resolve_provider(). Added resolve_api_key_provider_credentials()
and get_api_key_provider_status() helpers.
- runtime_provider.py: Added generic API-key provider branch in
resolve_runtime_provider() — any provider with auth_type='api_key'
is automatically handled.
- main.py: Added providers to hermes model menu with generic
_model_flow_api_key_provider() flow. Updated _has_any_provider_configured()
to check all provider env vars. Updated argparse --provider choices.
- setup.py: Added providers to setup wizard with API key prompts and
curated model lists.
- config.py: Added env vars (GLM_API_KEY, KIMI_API_KEY, MINIMAX_API_KEY,
etc.) to OPTIONAL_ENV_VARS.
- status.py: Added API key display and provider status section.
- doctor.py: Added connectivity checks for each provider endpoint.
- cli.py: Updated provider docstrings.
Docs: Updated README.md, .env.example, cli-config.yaml.example,
cli-commands.md, environment-variables.md, configuration.md.
Tests: 50 new tests covering registry, aliases, resolution, auto-detection,
credential resolution, and runtime provider dispatch.
Inspired by PR #33 (numman-ali) which proposed a provider registry approach.
Credit to tars90percent (PR #473) and manuelschipper (PR #420) for related
provider improvements merged earlier in this changeset.
2026-03-06 18:55:12 -08:00
|
|
|
# =========================================================================
|
|
|
|
|
# API-Key Providers
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ API-Key Providers", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
apikey_providers = {
|
|
|
|
|
"Z.AI / GLM": ("GLM_API_KEY", "ZAI_API_KEY", "Z_AI_API_KEY"),
|
|
|
|
|
"Kimi / Moonshot": ("KIMI_API_KEY",),
|
|
|
|
|
"MiniMax": ("MINIMAX_API_KEY",),
|
|
|
|
|
"MiniMax (China)": ("MINIMAX_CN_API_KEY",),
|
|
|
|
|
}
|
|
|
|
|
for pname, env_vars in apikey_providers.items():
|
|
|
|
|
key_val = ""
|
|
|
|
|
for ev in env_vars:
|
|
|
|
|
key_val = get_env_value(ev) or ""
|
|
|
|
|
if key_val:
|
|
|
|
|
break
|
|
|
|
|
configured = bool(key_val)
|
|
|
|
|
label = "configured" if configured else "not configured (run: hermes model)"
|
|
|
|
|
print(f" {pname:<16} {check_mark(configured)} {label}")
|
|
|
|
|
|
2026-02-02 19:01:51 -08:00
|
|
|
# =========================================================================
|
|
|
|
|
# Terminal Configuration
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Terminal Backend", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
2026-02-16 19:47:23 -08:00
|
|
|
terminal_env = os.getenv("TERMINAL_ENV", "")
|
|
|
|
|
if not terminal_env:
|
|
|
|
|
# Fall back to config file value when env var isn't set
|
|
|
|
|
# (hermes status doesn't go through cli.py's config loading)
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.config import load_config
|
|
|
|
|
_cfg = load_config()
|
|
|
|
|
terminal_env = _cfg.get("terminal", {}).get("backend", "local")
|
|
|
|
|
except Exception:
|
|
|
|
|
terminal_env = "local"
|
2026-02-02 19:01:51 -08:00
|
|
|
print(f" Backend: {terminal_env}")
|
|
|
|
|
|
|
|
|
|
if terminal_env == "ssh":
|
|
|
|
|
ssh_host = os.getenv("TERMINAL_SSH_HOST", "")
|
|
|
|
|
ssh_user = os.getenv("TERMINAL_SSH_USER", "")
|
|
|
|
|
print(f" SSH Host: {ssh_host or '(not set)'}")
|
|
|
|
|
print(f" SSH User: {ssh_user or '(not set)'}")
|
|
|
|
|
elif terminal_env == "docker":
|
|
|
|
|
docker_image = os.getenv("TERMINAL_DOCKER_IMAGE", "python:3.11-slim")
|
|
|
|
|
print(f" Docker Image: {docker_image}")
|
2026-03-05 00:44:39 -08:00
|
|
|
elif terminal_env == "daytona":
|
|
|
|
|
daytona_image = os.getenv("TERMINAL_DAYTONA_IMAGE", "nikolaik/python-nodejs:python3.11-nodejs20")
|
|
|
|
|
print(f" Daytona Image: {daytona_image}")
|
2026-02-02 19:01:51 -08:00
|
|
|
|
|
|
|
|
sudo_password = os.getenv("SUDO_PASSWORD", "")
|
|
|
|
|
print(f" Sudo: {check_mark(bool(sudo_password))} {'enabled' if sudo_password else 'disabled'}")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Messaging Platforms
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Messaging Platforms", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
platforms = {
|
|
|
|
|
"Telegram": ("TELEGRAM_BOT_TOKEN", "TELEGRAM_HOME_CHANNEL"),
|
|
|
|
|
"Discord": ("DISCORD_BOT_TOKEN", "DISCORD_HOME_CHANNEL"),
|
|
|
|
|
"WhatsApp": ("WHATSAPP_ENABLED", None),
|
fix: Signal adapter parity pass — integration gaps, clawdbot features, env var simplification
Integration gaps fixed (7 files missing Signal):
- cron/scheduler.py: Signal in platform_map (cron delivery was broken)
- agent/prompt_builder.py: PLATFORM_HINTS for Signal (agent knows it's on Signal)
- toolsets.py: hermes-signal toolset + added to hermes-gateway composite
- hermes_cli/status.py: Signal + Slack in platform status display
- tools/send_message_tool.py: Signal example in target description
- tools/cronjob_tools.py: Signal in delivery option docs + schema
- gateway/channel_directory.py: Signal in session-based channel discovery
Clawdbot parity features added to signal.py:
- Self-message filtering: prevents reply loops by checking sender != account
- SyncMessage filtering: ignores sync envelopes (sent transcripts, read receipts)
- Edit message support: reads dataMessage from editMessage envelope
- Mention rendering: replaces \uFFFC placeholders with @identifier text
- Jitter in SSE reconnection backoff (20% randomization, prevents thundering herd)
Env var simplification (7 → 4):
- Removed SIGNAL_DM_POLICY (DM auth follows standard platform pattern via
SIGNAL_ALLOWED_USERS + DM pairing, same as Telegram/Discord)
- Removed SIGNAL_GROUP_POLICY (derived from SIGNAL_GROUP_ALLOWED_USERS:
not set = disabled, set with IDs = allowlist, set with * = open)
- Removed SIGNAL_DEBUG (was setting root logger, removed entirely)
- Remaining: SIGNAL_HTTP_URL, SIGNAL_ACCOUNT (required),
SIGNAL_ALLOWED_USERS, SIGNAL_GROUP_ALLOWED_USERS (optional)
Updated all docs (website, AGENTS.md, signal.md) to match.
2026-03-08 21:00:21 -07:00
|
|
|
"Signal": ("SIGNAL_HTTP_URL", "SIGNAL_HOME_CHANNEL"),
|
|
|
|
|
"Slack": ("SLACK_BOT_TOKEN", None),
|
2026-02-02 19:01:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, (token_var, home_var) in platforms.items():
|
|
|
|
|
token = os.getenv(token_var, "")
|
|
|
|
|
has_token = bool(token)
|
|
|
|
|
|
|
|
|
|
home_channel = ""
|
|
|
|
|
if home_var:
|
|
|
|
|
home_channel = os.getenv(home_var, "")
|
|
|
|
|
|
|
|
|
|
status = "configured" if has_token else "not configured"
|
|
|
|
|
if home_channel:
|
|
|
|
|
status += f" (home: {home_channel})"
|
|
|
|
|
|
|
|
|
|
print(f" {name:<12} {check_mark(has_token)} {status}")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Gateway Status
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Gateway Service", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
if sys.platform.startswith('linux'):
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["systemctl", "--user", "is-active", "hermes-gateway"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True
|
|
|
|
|
)
|
|
|
|
|
is_active = result.stdout.strip() == "active"
|
|
|
|
|
print(f" Status: {check_mark(is_active)} {'running' if is_active else 'stopped'}")
|
|
|
|
|
print(f" Manager: systemd (user)")
|
|
|
|
|
|
|
|
|
|
elif sys.platform == 'darwin':
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["launchctl", "list", "ai.hermes.gateway"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True
|
|
|
|
|
)
|
|
|
|
|
is_loaded = result.returncode == 0
|
|
|
|
|
print(f" Status: {check_mark(is_loaded)} {'loaded' if is_loaded else 'not loaded'}")
|
|
|
|
|
print(f" Manager: launchd")
|
|
|
|
|
else:
|
|
|
|
|
print(f" Status: {color('N/A', Colors.DIM)}")
|
|
|
|
|
print(f" Manager: (not supported on this platform)")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Cron Jobs
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Scheduled Jobs", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
jobs_file = Path.home() / ".hermes" / "cron" / "jobs.json"
|
|
|
|
|
if jobs_file.exists():
|
|
|
|
|
import json
|
|
|
|
|
try:
|
2026-03-05 17:04:33 -05:00
|
|
|
with open(jobs_file, encoding="utf-8") as f:
|
2026-02-02 19:01:51 -08:00
|
|
|
data = json.load(f)
|
|
|
|
|
jobs = data.get("jobs", [])
|
|
|
|
|
enabled_jobs = [j for j in jobs if j.get("enabled", True)]
|
|
|
|
|
print(f" Jobs: {len(enabled_jobs)} active, {len(jobs)} total")
|
2026-02-20 23:23:32 -08:00
|
|
|
except Exception:
|
2026-02-02 19:01:51 -08:00
|
|
|
print(f" Jobs: (error reading jobs file)")
|
|
|
|
|
else:
|
|
|
|
|
print(f" Jobs: 0")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Sessions
|
|
|
|
|
# =========================================================================
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Sessions", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
sessions_file = Path.home() / ".hermes" / "sessions" / "sessions.json"
|
|
|
|
|
if sessions_file.exists():
|
|
|
|
|
import json
|
|
|
|
|
try:
|
2026-03-05 17:04:33 -05:00
|
|
|
with open(sessions_file, encoding="utf-8") as f:
|
2026-02-02 19:01:51 -08:00
|
|
|
data = json.load(f)
|
|
|
|
|
print(f" Active: {len(data)} session(s)")
|
2026-02-20 23:23:32 -08:00
|
|
|
except Exception:
|
2026-02-02 19:01:51 -08:00
|
|
|
print(f" Active: (error reading sessions file)")
|
|
|
|
|
else:
|
|
|
|
|
print(f" Active: 0")
|
|
|
|
|
|
|
|
|
|
# =========================================================================
|
|
|
|
|
# Deep checks
|
|
|
|
|
# =========================================================================
|
|
|
|
|
if deep:
|
|
|
|
|
print()
|
|
|
|
|
print(color("◆ Deep Checks", Colors.CYAN, Colors.BOLD))
|
|
|
|
|
|
|
|
|
|
# Check OpenRouter connectivity
|
|
|
|
|
openrouter_key = os.getenv("OPENROUTER_API_KEY", "")
|
|
|
|
|
if openrouter_key:
|
|
|
|
|
try:
|
|
|
|
|
import httpx
|
|
|
|
|
response = httpx.get(
|
2026-02-20 23:23:32 -08:00
|
|
|
OPENROUTER_MODELS_URL,
|
2026-02-02 19:01:51 -08:00
|
|
|
headers={"Authorization": f"Bearer {openrouter_key}"},
|
|
|
|
|
timeout=10
|
|
|
|
|
)
|
|
|
|
|
ok = response.status_code == 200
|
|
|
|
|
print(f" OpenRouter: {check_mark(ok)} {'reachable' if ok else f'error ({response.status_code})'}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f" OpenRouter: {check_mark(False)} error: {e}")
|
|
|
|
|
|
|
|
|
|
# Check gateway port
|
|
|
|
|
try:
|
|
|
|
|
import socket
|
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
sock.settimeout(1)
|
|
|
|
|
result = sock.connect_ex(('127.0.0.1', 18789))
|
|
|
|
|
sock.close()
|
|
|
|
|
# Port in use = gateway likely running
|
|
|
|
|
port_in_use = result == 0
|
|
|
|
|
# This is informational, not necessarily bad
|
|
|
|
|
print(f" Port 18789: {'in use' if port_in_use else 'available'}")
|
2026-02-20 23:23:32 -08:00
|
|
|
except OSError:
|
2026-02-02 19:01:51 -08:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print(color("─" * 60, Colors.DIM))
|
|
|
|
|
print(color(" Run 'hermes doctor' for detailed diagnostics", Colors.DIM))
|
|
|
|
|
print(color(" Run 'hermes setup' to configure", Colors.DIM))
|
|
|
|
|
print()
|