Merge origin/main into bb/gui

Adopt main's web/ dashboard layout (apps/dashboard removed; web/ restored),
keep bb/gui's desktop CLI/update workspace handling, and preserve main's
mTLS/URL validation MCP changes. Dashboard backend is aligned to main with
only the intended STT provider quarantine/ElevenLabs override reapplied.
This commit is contained in:
Brooklyn Nicholson
2026-05-29 20:40:08 -05:00
1205 changed files with 39074 additions and 9768 deletions
+94 -9
View File
@@ -10,6 +10,7 @@ rendered with Rich Markdown. Otherwise a default confirmation is shown.
from __future__ import annotations
import functools
import json
import logging
import os
import shutil
@@ -780,7 +781,29 @@ def _discover_all_plugins() -> list:
return list(seen.values())
def cmd_list() -> None:
def _plugin_status(name: str, enabled: set, disabled: set) -> str:
"""Return the user-facing activation state for a plugin name."""
if name in disabled:
return "disabled"
if name in enabled:
return "enabled"
return "not enabled"
def _filter_plugin_entries(entries: list, args: Any, enabled: set, disabled: set) -> list:
"""Apply ``hermes plugins list`` CLI filters."""
filtered = entries
if getattr(args, "no_bundled", False) or getattr(args, "user", False):
filtered = [entry for entry in filtered if entry[3] != "bundled"]
if getattr(args, "enabled", False):
filtered = [
entry for entry in filtered
if _plugin_status(entry[0], enabled, disabled) == "enabled"
]
return filtered
def cmd_list(args: Any | None = None) -> None:
"""List all plugins (bundled + user) with enabled/disabled state."""
from rich.console import Console
from rich.table import Table
@@ -794,6 +817,31 @@ def cmd_list() -> None:
enabled = _get_enabled_set()
disabled = _get_disabled_set()
entries = _filter_plugin_entries(entries, args, enabled, disabled)
if getattr(args, "json", False):
payload = [
{
"name": name,
"status": _plugin_status(name, enabled, disabled),
"version": str(version),
"description": description,
"source": source,
}
for name, version, description, source, _dir in entries
]
print(json.dumps(payload, indent=2))
return
if getattr(args, "plain", False):
for name, version, _description, source, _dir in entries:
status = _plugin_status(name, enabled, disabled)
print(f"{status:12} {source:8} {str(version):8} {name}")
return
if not entries:
console.print("[dim]No plugins matched the selected filters.[/dim]")
return
table = Table(title="Plugins", show_lines=False)
table.add_column("Name", style="bold")
@@ -803,9 +851,10 @@ def cmd_list() -> None:
table.add_column("Source", style="dim")
for name, version, description, source, _dir in entries:
if name in disabled:
status_name = _plugin_status(name, enabled, disabled)
if status_name == "disabled":
status = "[red]disabled[/red]"
elif name in enabled:
elif status_name == "enabled":
status = "[green]enabled[/green]"
else:
status = "[yellow]not enabled[/yellow]"
@@ -814,6 +863,7 @@ def cmd_list() -> None:
console.print()
console.print(table)
console.print()
console.print("[dim]Compact view:[/dim] hermes plugins list --plain --no-bundled")
console.print("[dim]Interactive toggle:[/dim] hermes plugins")
console.print("[dim]Enable/disable:[/dim] hermes plugins enable/disable <name>")
console.print("[dim]Plugins are opt-in by default — only 'enabled' plugins load.[/dim]")
@@ -834,12 +884,35 @@ def _discover_memory_providers() -> list[tuple[str, str]]:
def _discover_context_engines() -> list[tuple[str, str]]:
"""Return [(name, description), ...] for available context engines."""
"""Return [(name, description), ...] for available context engines.
Includes repo-shipped engines from ``plugins/context_engine/`` AND
plugin-registered engines (third-party engines installed as Hermes
plugins via ``ctx.register_context_engine``). Repo-shipped descriptions
win when a plugin-registered engine collides on name.
"""
engines: list[tuple[str, str]] = []
seen: set[str] = set()
try:
from plugins.context_engine import discover_context_engines
return [(name, desc) for name, desc, _avail in discover_context_engines()]
for name, desc, _avail in discover_context_engines():
if name not in seen:
engines.append((name, desc))
seen.add(name)
except Exception:
return []
pass
try:
from hermes_cli.plugins import discover_plugins, get_plugin_context_engine
discover_plugins()
plugin_engine = get_plugin_context_engine()
if plugin_engine and getattr(plugin_engine, "name", None) and plugin_engine.name not in seen:
engines.append((plugin_engine.name, "installed plugin"))
except Exception:
pass
return engines
def _get_current_memory_provider() -> str:
@@ -1057,7 +1130,7 @@ def _run_composite_ui(curses, plugin_names, plugin_labels, plugin_selected,
stdscr.addnstr(0, 0, "Plugins", max_x - 1, hattr)
stdscr.addnstr(
1, 0,
" \u2191\u2193 navigate SPACE toggle ENTER configure/confirm ESC done",
" ↑↓/j/k navigate PgUp/PgDn page SPACE toggle ENTER configure/confirm ESC done",
max_x - 1, curses.A_DIM,
)
except curses.error:
@@ -1097,7 +1170,9 @@ def _run_composite_ui(curses, plugin_names, plugin_labels, plugin_selected,
pass
y += 1
for i in range(n_plugins):
plugin_start = scroll_offset
plugin_stop = min(n_plugins, scroll_offset + max(visible_rows, 0))
for i in range(plugin_start, plugin_stop):
if y >= max_y - 1:
break
check = "\u2713" if i in chosen else " "
@@ -1155,6 +1230,16 @@ def _run_composite_ui(curses, plugin_names, plugin_labels, plugin_selected,
elif key in {curses.KEY_DOWN, ord("j")}:
if total_items > 0:
cursor = (cursor + 1) % total_items
elif key in {curses.KEY_NPAGE, ord("f")}:
if total_items > 0:
cursor = min(total_items - 1, cursor + max(1, max_y - 5))
elif key in {curses.KEY_PPAGE, ord("b")}:
if total_items > 0:
cursor = max(0, cursor - max(1, max_y - 5))
elif key == curses.KEY_HOME:
cursor = 0
elif key == curses.KEY_END:
cursor = max(0, total_items - 1)
elif key == ord(" "):
if cursor < n_plugins:
# Toggle general plugin
@@ -1596,7 +1681,7 @@ def plugins_command(args) -> None:
elif action == "disable":
cmd_disable(args.name)
elif action in {"list", "ls"}:
cmd_list()
cmd_list(args)
elif action is None:
cmd_toggle()
else: