From dee41d0716efccc3d5ef9125d6aa0a55dcdb8ff9 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 29 Jun 2026 11:10:15 +1000 Subject: [PATCH] feat(dashboard): catalogue all memory-provider API keys in OPTIONAL_ENV_VARS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard Keys page and `hermes setup` render API-key rows from OPTIONAL_ENV_VARS, but only Honcho had an entry — so Hindsight, Supermemory, Mem0, RetainDB, ByteRover, and OpenViking read their keys straight from os.environ yet had no place to set them in the GUI. Add catalog entries (category=tool, password-masked, with get-key URLs and the tool each powers) for all six, plus the relevant base-URL/endpoint companions. Pure declaration: the generic GET /api/env endpoint, the save/reveal write path, and the sandbox env blocklist (which auto-derives from tool-category OPTIONAL_ENV_VARS) all pick these up with no further wiring. Adds a behavior-contract test asserting every memory provider's primary credential key is catalogued, tool-categorised, and password-masked. --- hermes_cli/config.py | 77 +++++++++++++++++++++++++++++++++ tests/hermes_cli/test_config.py | 43 ++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index d0ef0c3a205..71d23a2c138 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -3649,6 +3649,83 @@ OPTIONAL_ENV_VARS = { "category": "tool", }, + # ── Hindsight ── + "HINDSIGHT_API_KEY": { + "description": "Hindsight API key for graph-aware persistent memory", + "prompt": "Hindsight API key", + "url": "https://hindsight.vectorize.io", + "tools": ["hindsight_recall"], + "password": True, + "category": "tool", + }, + "HINDSIGHT_API_URL": { + "description": "Base URL for the Hindsight API (default: https://api.hindsight.vectorize.io)", + "prompt": "Hindsight API URL", + "category": "tool", + "advanced": True, + }, + + # ── Supermemory ── + "SUPERMEMORY_API_KEY": { + "description": "Supermemory API key for conversation-scoped persistent memory", + "prompt": "Supermemory API key", + "url": "https://supermemory.ai", + "tools": ["supermemory_search"], + "password": True, + "category": "tool", + }, + + # ── Mem0 ── + "MEM0_API_KEY": { + "description": "Mem0 Platform API key for semantic persistent memory", + "prompt": "Mem0 API key", + "url": "https://app.mem0.ai", + "tools": ["mem0_search"], + "password": True, + "category": "tool", + }, + + # ── RetainDB ── + "RETAINDB_API_KEY": { + "description": "RetainDB API key for persistent memory", + "prompt": "RetainDB API key", + "url": "https://retaindb.com", + "tools": ["retaindb_search"], + "password": True, + "category": "tool", + }, + "RETAINDB_BASE_URL": { + "description": "Base URL for self-hosted RetainDB instances (default: https://api.retaindb.com)", + "prompt": "RetainDB base URL", + "category": "tool", + "advanced": True, + }, + + # ── ByteRover ── + "BRV_API_KEY": { + "description": "ByteRover API key (optional, for cloud sync — local-first by default)", + "prompt": "ByteRover API key", + "url": "https://app.byterover.dev", + "tools": ["brv_query"], + "password": True, + "category": "tool", + }, + + # ── OpenViking ── + "OPENVIKING_API_KEY": { + "description": "OpenViking API key (leave blank for local dev mode)", + "prompt": "OpenViking API key", + "tools": ["viking_search"], + "password": True, + "category": "tool", + }, + "OPENVIKING_ENDPOINT": { + "description": "OpenViking server URL (default: http://127.0.0.1:1933)", + "prompt": "OpenViking endpoint", + "category": "tool", + "advanced": True, + }, + # ── Langfuse observability ── "HERMES_LANGFUSE_PUBLIC_KEY": { "description": "Langfuse project public key (pk-lf-...)", diff --git a/tests/hermes_cli/test_config.py b/tests/hermes_cli/test_config.py index 979733a4337..266ac26d1fa 100644 --- a/tests/hermes_cli/test_config.py +++ b/tests/hermes_cli/test_config.py @@ -700,6 +700,49 @@ class TestOptionalEnvVarsRegistry: assert "HERMES_MAX_ITERATIONS" not in OPTIONAL_ENV_VARS +class TestMemoryProviderEnvVarsRegistry: + """Every memory provider that reads an API key from the environment must + have that key catalogued in OPTIONAL_ENV_VARS so the dashboard Keys page + and `hermes setup` surface it (previously only Honcho was listed, leaving + Hindsight/Supermemory/Mem0/RetainDB/ByteRover/OpenViking invisible). + + This is a behavior contract, not a snapshot: it asserts each provider's + primary credential key is present, tool-categorised, and password-masked — + not a frozen count of entries. + """ + + # provider primary-credential env key -> the tool-call name it powers. + MEMORY_PROVIDER_KEYS = { + "HONCHO_API_KEY": "honcho_context", + "HINDSIGHT_API_KEY": "hindsight_recall", + "SUPERMEMORY_API_KEY": "supermemory_search", + "MEM0_API_KEY": "mem0_search", + "RETAINDB_API_KEY": "retaindb_search", + "BRV_API_KEY": "brv_query", + "OPENVIKING_API_KEY": "viking_search", + } + + def test_memory_provider_keys_are_catalogued(self): + from hermes_cli.config import OPTIONAL_ENV_VARS + missing = [k for k in self.MEMORY_PROVIDER_KEYS if k not in OPTIONAL_ENV_VARS] + assert not missing, f"memory provider keys missing from OPTIONAL_ENV_VARS: {missing}" + + def test_memory_provider_keys_are_tool_category(self): + from hermes_cli.config import OPTIONAL_ENV_VARS + for key in self.MEMORY_PROVIDER_KEYS: + assert OPTIONAL_ENV_VARS[key]["category"] == "tool", key + + def test_memory_provider_keys_are_password_masked(self): + from hermes_cli.config import OPTIONAL_ENV_VARS + for key in self.MEMORY_PROVIDER_KEYS: + assert OPTIONAL_ENV_VARS[key].get("password") is True, key + + def test_memory_provider_keys_advertise_their_tool(self): + from hermes_cli.config import OPTIONAL_ENV_VARS + for key, tool in self.MEMORY_PROVIDER_KEYS.items(): + assert tool in OPTIONAL_ENV_VARS[key].get("tools", []), key + + class TestConfigMigrationSecretPrompts: def test_required_secret_env_prompt_uses_masked_prompt(self, tmp_path, monkeypatch): from hermes_cli import config as cfg_mod