refactor(hermes_cli): skin_engine/slack_cli/slash_exec/telegram_managed_bot/suggestions_cmd/skills_config/skin_cmd — collapse mapping/normalization helpers, pack data tables, compact docstrings
This commit is contained in:
+24
-82
@@ -11,20 +11,10 @@ from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
__all__ = [
|
||||
"CommandContext",
|
||||
"CommandReply",
|
||||
"EXECUTORS",
|
||||
"execute_command",
|
||||
"resolve_executor",
|
||||
"run_execute",
|
||||
]
|
||||
__all__ = ["CommandContext", "CommandReply", "EXECUTORS", "execute_command", "resolve_executor",
|
||||
"run_execute"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Context / reply dataclasses
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommandContext:
|
||||
"""Surface-provided inputs for a shared command executor."""
|
||||
@@ -37,22 +27,15 @@ class CommandContext:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommandReply:
|
||||
"""Canonical result of a shared executor.
|
||||
|
||||
``text`` is the surface-independent core text. ``data`` carries the structured values the
|
||||
executor derived so a surface may re-render them with its own decoration (Rich columns, markdown
|
||||
bullets) without duplicating the computation. ``format`` is a rendering hint only.
|
||||
"""
|
||||
"""Canonical result of a shared executor: surface-independent ``text`` plus the structured
|
||||
``data`` it derived so a surface can re-render with its own decoration."""
|
||||
|
||||
text: str
|
||||
data: Mapping[str, Any] = field(default_factory=dict)
|
||||
format: str = "plain" # "plain" | "markdown" (hint, not a contract)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Executors — pure formatters, no agent/session mutation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Executors — pure formatters, no agent/session mutation.
|
||||
def _exec_version(ctx: CommandContext) -> CommandReply:
|
||||
"""Core /version text — the banner version label."""
|
||||
from hermes_cli.banner import format_banner_version_label
|
||||
@@ -70,13 +53,11 @@ def _exec_egress(ctx: CommandContext) -> CommandReply:
|
||||
def _exec_profile(ctx: CommandContext) -> CommandReply:
|
||||
"""Core /profile data — active profile name + home directory.
|
||||
|
||||
A multiplexed gateway may pre-resolve the per-source profile/home and pass them via
|
||||
``options`` (``profile_name`` / ``home_display``); otherwise process-level values are used,
|
||||
matching the old CLI and non-multiplex gateway behavior.
|
||||
A multiplexed gateway may pre-resolve the per-source profile/home via ``options``
|
||||
(``profile_name`` / ``home_display``); otherwise process-level values are used.
|
||||
"""
|
||||
profile_name = str(ctx.options.get("profile_name") or "").strip()
|
||||
home_display = str(ctx.options.get("home_display") or "").strip()
|
||||
|
||||
if not profile_name:
|
||||
from hermes_cli.profiles import get_active_profile_name
|
||||
|
||||
@@ -85,26 +66,17 @@ def _exec_profile(ctx: CommandContext) -> CommandReply:
|
||||
from hermes_constants import display_hermes_home
|
||||
|
||||
home_display = display_hermes_home()
|
||||
|
||||
# Presentation-only display name (profile.yaml). `data.profile` stays
|
||||
# the canonical id — consumers route on it; only the text gets the label.
|
||||
# Presentation-only display name (profile.yaml); `data.profile` stays the canonical id.
|
||||
label = profile_name
|
||||
try:
|
||||
from hermes_cli.profiles import (
|
||||
format_profile_label,
|
||||
get_profile_dir,
|
||||
read_profile_meta,
|
||||
)
|
||||
from hermes_cli.profiles import format_profile_label, get_profile_dir, read_profile_meta
|
||||
|
||||
display = read_profile_meta(get_profile_dir(profile_name)).get("display_name", "")
|
||||
label = format_profile_label(profile_name, display)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return CommandReply(
|
||||
f"Profile: {label}\nHome: {home_display}",
|
||||
data={"profile": profile_name, "home": home_display},
|
||||
)
|
||||
return CommandReply(f"Profile: {label}\nHome: {home_display}",
|
||||
data={"profile": profile_name, "home": home_display})
|
||||
|
||||
|
||||
def _exec_bundles(ctx: CommandContext) -> CommandReply:
|
||||
@@ -112,11 +84,7 @@ def _exec_bundles(ctx: CommandContext) -> CommandReply:
|
||||
try:
|
||||
from agent.skill_bundles import _bundles_dir, list_bundles
|
||||
except Exception as exc: # pragma: no cover - env-specific
|
||||
return CommandReply(
|
||||
f"Bundles subsystem unavailable: {exc}",
|
||||
data={"error": str(exc)},
|
||||
)
|
||||
|
||||
return CommandReply(f"Bundles subsystem unavailable: {exc}", data={"error": str(exc)})
|
||||
bundles = list_bundles()
|
||||
bundles_dir = str(_bundles_dir())
|
||||
if not bundles:
|
||||
@@ -124,21 +92,15 @@ def _exec_bundles(ctx: CommandContext) -> CommandReply:
|
||||
"No skill bundles installed.\n"
|
||||
"Create one with: hermes bundles create <name> --skill <s1> --skill <s2>\n"
|
||||
f"Directory: {bundles_dir}",
|
||||
data={"bundles": [], "dir": bundles_dir},
|
||||
)
|
||||
|
||||
data={"bundles": [], "dir": bundles_dir})
|
||||
lines = [f"Skill Bundles ({len(bundles)} installed):"]
|
||||
for info in bundles:
|
||||
skill_count = len(info.get("skills", []))
|
||||
desc = info.get("description") or f"Load {skill_count} skills"
|
||||
lines.append(f"/{info['slug']} — {desc} ({skill_count} skills)")
|
||||
for s in info.get("skills", []):
|
||||
lines.append(f" · {s}")
|
||||
lines.extend(f" · {s}" for s in info.get("skills", []))
|
||||
lines.append("Invoke a bundle with /<slug> to load all its skills.")
|
||||
return CommandReply(
|
||||
"\n".join(lines),
|
||||
data={"bundles": bundles, "dir": bundles_dir},
|
||||
)
|
||||
return CommandReply("\n".join(lines), data={"bundles": bundles, "dir": bundles_dir})
|
||||
|
||||
|
||||
def _skill_commands() -> dict:
|
||||
@@ -156,18 +118,14 @@ def _exec_help(ctx: CommandContext) -> CommandReply:
|
||||
from agent.i18n import t
|
||||
from hermes_cli.commands import gateway_help_lines
|
||||
|
||||
lines = [
|
||||
t("gateway.help.header"),
|
||||
*gateway_help_lines(),
|
||||
]
|
||||
lines = [t("gateway.help.header"), *gateway_help_lines()]
|
||||
skill_cmds = _skill_commands()
|
||||
try:
|
||||
if skill_cmds:
|
||||
lines.append(t("gateway.help.skill_header", count=len(skill_cmds)))
|
||||
# Show first 10, then point to /commands for the rest
|
||||
sorted_cmds = sorted(skill_cmds)
|
||||
for cmd in sorted_cmds[:10]:
|
||||
lines.append(f"`{cmd}` — {skill_cmds[cmd]['description']}")
|
||||
sorted_cmds = sorted(skill_cmds) # first 10, then point to /commands for the rest
|
||||
lines.extend(f"`{cmd}` — {skill_cmds[cmd]['description']}"
|
||||
for cmd in sorted_cmds[:10])
|
||||
if len(sorted_cmds) > 10:
|
||||
lines.append(t("gateway.help.more_use_commands", count=len(sorted_cmds) - 10))
|
||||
except Exception:
|
||||
@@ -178,8 +136,7 @@ def _exec_help(ctx: CommandContext) -> CommandReply:
|
||||
def _exec_commands(ctx: CommandContext) -> CommandReply:
|
||||
"""Core gateway /commands body — paginated command + skill listing.
|
||||
|
||||
``ctx.options["page_size"]`` is a surface parameter (Telegram uses 15, everything else 20) — for
|
||||
a fixed context the text is surface-invariant.
|
||||
``ctx.options["page_size"]`` is a surface parameter (Telegram uses 15, everything else 20).
|
||||
"""
|
||||
from agent.i18n import t
|
||||
from hermes_cli.commands import gateway_help_lines
|
||||
@@ -193,7 +150,6 @@ def _exec_commands(ctx: CommandContext) -> CommandReply:
|
||||
else:
|
||||
requested_page = 1
|
||||
|
||||
# Build combined entry list: built-in commands + skill commands
|
||||
entries = list(gateway_help_lines())
|
||||
skill_cmds = _skill_commands()
|
||||
try:
|
||||
@@ -216,13 +172,8 @@ def _exec_commands(ctx: CommandContext) -> CommandReply:
|
||||
total_pages = max(1, (len(entries) + page_size - 1) // page_size)
|
||||
page = max(1, min(requested_page, total_pages))
|
||||
start = (page - 1) * page_size
|
||||
page_entries = entries[start:start + page_size]
|
||||
|
||||
lines = [
|
||||
t("gateway.commands.header", total=len(entries), page=page, total_pages=total_pages),
|
||||
"",
|
||||
*page_entries,
|
||||
]
|
||||
lines = [t("gateway.commands.header", total=len(entries), page=page, total_pages=total_pages),
|
||||
"", *entries[start:start + page_size]]
|
||||
if total_pages > 1:
|
||||
nav_parts = []
|
||||
if page > 1:
|
||||
@@ -235,18 +186,13 @@ def _exec_commands(ctx: CommandContext) -> CommandReply:
|
||||
return CommandReply("\n".join(lines), format="markdown")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registry + resolution
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
EXECUTORS: dict[str, Callable[[CommandContext], CommandReply]] = {
|
||||
"version": _exec_version,
|
||||
"egress": _exec_egress,
|
||||
"profile": _exec_profile,
|
||||
"bundles": _exec_bundles,
|
||||
"gateway_help": _exec_help,
|
||||
"gateway_commands": _exec_commands,
|
||||
}
|
||||
"gateway_commands": _exec_commands}
|
||||
|
||||
|
||||
def resolve_executor(cmd_def: Any) -> Callable[[CommandContext], CommandReply] | None:
|
||||
@@ -261,11 +207,7 @@ def run_execute(cmd_def: Any, ctx: CommandContext) -> CommandReply | None:
|
||||
|
||||
|
||||
def execute_command(name: str, ctx: CommandContext) -> CommandReply:
|
||||
"""Run the shared executor for the command named ``name``.
|
||||
|
||||
Raises ``LookupError`` when the command is unknown or not migrated — call sites use this only
|
||||
for commands they know carry ``execute``.
|
||||
"""
|
||||
"""Run the shared executor for ``name``; ``LookupError`` when unknown or not migrated."""
|
||||
from hermes_cli.commands import resolve_command
|
||||
|
||||
cmd_def = resolve_command(name)
|
||||
|
||||
Reference in New Issue
Block a user