From d1189f2be90582934de28c4bf10d9792b91fbf11 Mon Sep 17 00:00:00 2001 From: Erosika Date: Mon, 30 Mar 2026 14:10:01 -0400 Subject: [PATCH] feat(honcho): add cross-profile observability for Honcho integration - hermes honcho status: shows active profile name + host key - hermes honcho status --all: compact table of all profiles with mode, recall, write frequency per host block - hermes honcho peers: cross-profile peer identity table (user peer, AI peer, linked hosts) - All write commands (peer, mode, tokens) print [host_key] label when operating on a non-default profile --- hermes_cli/main.py | 4 +- honcho_integration/cli.py | 132 ++++++++++++++++++++++++++++++++++---- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 6514a5581..6cb22c4d5 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -4514,7 +4514,9 @@ For more help on a command: honcho_subparsers = honcho_parser.add_subparsers(dest="honcho_command") honcho_subparsers.add_parser("setup", help="Interactive setup wizard for Honcho integration") - honcho_subparsers.add_parser("status", help="Show current Honcho config and connection status") + honcho_status = honcho_subparsers.add_parser("status", help="Show current Honcho config and connection status") + honcho_status.add_argument("--all", action="store_true", help="Show config overview across all profiles") + honcho_subparsers.add_parser("peers", help="Show peer identities across all profiles") honcho_subparsers.add_parser("sessions", help="List known Honcho session mappings") honcho_map = honcho_subparsers.add_parser( diff --git a/honcho_integration/cli.py b/honcho_integration/cli.py index 12806248e..a3856ed3a 100644 --- a/honcho_integration/cli.py +++ b/honcho_integration/cli.py @@ -240,8 +240,52 @@ def cmd_setup(args) -> None: print(" hermes honcho map — map this directory to a session name\n") +def _active_profile_name() -> str: + """Return the active Hermes profile name.""" + try: + from hermes_cli.profiles import get_active_profile_name + return get_active_profile_name() + except Exception: + return "default" + + +def _all_profile_host_configs() -> list[tuple[str, str, dict]]: + """Return (profile_name, host_key, host_block) for every known profile. + + Reads honcho.json once and maps each profile to its host block. + """ + try: + from honcho_integration.client import HOST + from hermes_cli.profiles import list_profiles + profiles = list_profiles() + except Exception: + return [(_active_profile_name(), _host_key(), {})] + + cfg = _read_config() + hosts = cfg.get("hosts", {}) + results = [] + + # Default profile + default_block = hosts.get(HOST, {}) + results.append(("default", HOST, default_block)) + + for p in profiles: + if p.name == "default": + continue + h = f"{HOST}.{p.name}" + results.append((p.name, h, hosts.get(h, {}))) + + return results + + def cmd_status(args) -> None: """Show current Honcho config and connection status.""" + show_all = getattr(args, "all", False) + + if show_all: + _cmd_status_all() + return + try: import honcho # noqa: F401 except ImportError: @@ -268,11 +312,16 @@ def cmd_status(args) -> None: api_key = hcfg.api_key or "" masked = f"...{api_key[-8:]}" if len(api_key) > 8 else ("set" if api_key else "not set") - print("\nHoncho status\n" + "─" * 40) + profile = _active_profile_name() + profile_label = f" [{hcfg.host}]" if profile != "default" else "" + + print(f"\nHoncho status{profile_label}\n" + "─" * 40) + if profile != "default": + print(f" Profile: {profile}") + print(f" Host: {hcfg.host}") print(f" Enabled: {hcfg.enabled}") print(f" API key: {masked}") print(f" Workspace: {hcfg.workspace_id}") - print(f" Host: {hcfg.host}") print(f" Config path: {active_path}") if write_path != active_path: print(f" Write path: {write_path} (instance-local)") @@ -299,6 +348,52 @@ def cmd_status(args) -> None: print(f"\n Not connected ({reason})\n") +def _cmd_status_all() -> None: + """Show Honcho config overview across all profiles.""" + rows = _all_profile_host_configs() + cfg = _read_config() + active = _active_profile_name() + + print(f"\nHoncho profiles ({len(rows)})\n" + "─" * 60) + print(f" {'Profile':<14} {'Host':<22} {'Enabled':<9} {'Mode':<9} {'Recall':<9} {'Write'}") + print(f" {'─' * 14} {'─' * 22} {'─' * 9} {'─' * 9} {'─' * 9} {'─' * 9}") + + for name, host, block in rows: + enabled = block.get("enabled", cfg.get("enabled")) + if enabled is None: + # Auto-enable check: any credentials? + has_creds = bool(cfg.get("apiKey") or os.environ.get("HONCHO_API_KEY")) + enabled = has_creds if block else False + enabled_str = "yes" if enabled else "no" + + mode = block.get("memoryMode") or cfg.get("memoryMode", "hybrid") + recall = block.get("recallMode") or cfg.get("recallMode", "hybrid") + write = block.get("writeFrequency") or cfg.get("writeFrequency", "async") + + marker = " *" if name == active else "" + print(f" {name + marker:<14} {host:<22} {enabled_str:<9} {mode:<9} {recall:<9} {write}") + + print(f"\n * active profile\n") + + +def cmd_peers(args) -> None: + """Show peer identities across all profiles.""" + rows = _all_profile_host_configs() + cfg = _read_config() + + print(f"\nHoncho peer identities ({len(rows)} profiles)\n" + "─" * 60) + print(f" {'Profile':<14} {'User peer':<16} {'AI peer':<22} {'Linked hosts'}") + print(f" {'─' * 14} {'─' * 16} {'─' * 22} {'─' * 16}") + + for name, host, block in rows: + user = block.get("peerName") or cfg.get("peerName") or "(not set)" + ai = block.get("aiPeer") or cfg.get("aiPeer") or host + linked = ", ".join(block.get("linkedHosts", [])) or "--" + print(f" {name:<14} {user:<16} {ai:<22} {linked}") + + print() + + def cmd_sessions(args) -> None: """List known directory → session name mappings.""" cfg = _read_config() @@ -373,23 +468,26 @@ def cmd_peer(args) -> None: print(f" Dialectic cap: {max_chars} chars\n") return + host = _host_key() + label = f"[{host}] " if host != "hermes" else "" + if user_name is not None: - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["peerName"] = user_name.strip() + cfg.setdefault("hosts", {}).setdefault(host, {})["peerName"] = user_name.strip() changed = True - print(f" User peer → {user_name.strip()}") + print(f" {label}User peer -> {user_name.strip()}") if ai_name is not None: - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["aiPeer"] = ai_name.strip() + cfg.setdefault("hosts", {}).setdefault(host, {})["aiPeer"] = ai_name.strip() changed = True - print(f" AI peer → {ai_name.strip()}") + print(f" {label}AI peer -> {ai_name.strip()}") if reasoning is not None: if reasoning not in REASONING_LEVELS: print(f" Invalid reasoning level '{reasoning}'. Options: {', '.join(REASONING_LEVELS)}") return - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["dialecticReasoningLevel"] = reasoning + cfg.setdefault("hosts", {}).setdefault(host, {})["dialecticReasoningLevel"] = reasoning changed = True - print(f" Dialectic reasoning level → {reasoning}") + print(f" {label}Dialectic reasoning level -> {reasoning}") if changed: _write_config(cfg) @@ -422,9 +520,11 @@ def cmd_mode(args) -> None: print(f" Invalid mode '{mode_arg}'. Options: {', '.join(MODES)}\n") return - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["memoryMode"] = mode_arg + host = _host_key() + label = f"[{host}] " if host != "hermes" else "" + cfg.setdefault("hosts", {}).setdefault(host, {})["memoryMode"] = mode_arg _write_config(cfg) - print(f" Memory mode → {mode_arg} ({MODES[mode_arg]})\n") + print(f" {label}Memory mode -> {mode_arg} ({MODES[mode_arg]})\n") def cmd_tokens(args) -> None: @@ -454,14 +554,16 @@ def cmd_tokens(args) -> None: print("\n Set with: hermes honcho tokens [--context N] [--dialectic N]\n") return + host = _host_key() + label = f"[{host}] " if host != "hermes" else "" changed = False if context is not None: - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["contextTokens"] = context - print(f" context tokens → {context}") + cfg.setdefault("hosts", {}).setdefault(host, {})["contextTokens"] = context + print(f" {label}context tokens -> {context}") changed = True if dialectic is not None: - cfg.setdefault("hosts", {}).setdefault(_host_key(), {})["dialecticMaxChars"] = dialectic - print(f" dialectic cap → {dialectic} chars") + cfg.setdefault("hosts", {}).setdefault(host, {})["dialecticMaxChars"] = dialectic + print(f" {label}dialectic cap -> {dialectic} chars") changed = True if changed: @@ -778,6 +880,8 @@ def honcho_command(args) -> None: cmd_setup(args) elif sub == "status": cmd_status(args) + elif sub == "peers": + cmd_peers(args) elif sub == "sessions": cmd_sessions(args) elif sub == "map":