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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user