honcho/honcho-cli/src/honcho_cli/_help.py

131 lines
5.2 KiB
Python

"""Themed help rendering for honcho CLI.
Single source of truth for:
- Rich-utils theme constants (dim borders, brand color)
- HonchoTyperGroup: subclass applied via ``cls=`` at every Typer app in
this package — replaces Click's terse ``Usage: …`` line with
pattern/example rows and prints a curated welcome at the top-level.
Lives in its own module so every ``commands/*.py`` can import it without
pulling in ``main.py`` (which would create an import cycle).
"""
from __future__ import annotations
import click
import typer.rich_utils as ru
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from typer.core import TyperGroup
from honcho_cli import __version__
from honcho_cli.branding import BANNER, BRAND
from honcho_cli.output import use_json
# Theme Typer's rich help renderer. Module-level side effect limited to
# styling — no behavior changes that could surprise other Typer users.
ru.STYLE_COMMANDS_PANEL_BORDER = "dim"
ru.STYLE_OPTIONS_PANEL_BORDER = "dim"
ru.STYLE_ERRORS_PANEL_BORDER = "dim"
ru.STYLE_OPTION = f"bold {BRAND}"
ru.STYLE_SWITCH = f"bold {BRAND}"
ru.STYLE_USAGE = "dim"
ru.STYLE_USAGE_COMMAND = f"bold {BRAND}"
def _cmd_table(rows: list[tuple[str, str]]) -> Table:
t = Table(show_header=False, box=None, padding=(0, 2, 0, 0), expand=False)
t.add_column("cmd", style=f"bold {BRAND}", no_wrap=True)
t.add_column("desc", style="default")
for cmd, desc in rows:
t.add_row(cmd, desc)
return t
def _welcome_panel(title: str, rows: list[tuple[str, str]]) -> Panel:
return Panel(
_cmd_table(rows),
title=f"[dim]{title}[/dim]",
title_align="left",
border_style="dim",
box=box.ROUNDED,
padding=(0, 1),
expand=False,
)
def print_welcome(console: Console) -> None:
"""Render the curated 3-panel welcome (banner + getting started / memory / commands)."""
if use_json():
return
console.print(f"[bold {BRAND}]{BANNER}[/bold {BRAND}]")
console.print(f" [dim]v{__version__}[/dim]\n", highlight=False)
start_rows = [
("honcho init", "configure API key and server URL"),
("honcho doctor", "verify connection and workspace health"),
]
cmd_rows = [
("[dim]pattern[/dim]", r"[dim]honcho <command> \[args] \[-w workspace] \[-p peer] \[-s session][/dim]"),
("[dim]example[/dim]", "[dim]honcho peer chat \"what does alice prefer?\" -p alice -w agents[/dim]"),
("", ""),
("workspace", "list · create · search · delete · inspect · queue-status"),
("peer", "list · create · search · inspect · card · chat"),
("", "get-metadata · set-metadata · representation"),
("session", "list · create · search · delete · inspect · add-peers"),
("", "context · get-metadata · set-metadata · peers"),
("", "remove-peers · representation · summaries"),
("message", "list · create · get"),
("conclusion", "list · create · search · delete"),
("config", "inspect current configuration"),
]
memory_rows = [
("honcho peer chat \"...\" -p <peer> -w <workspace>","query the Dialectic about a peer"),
("honcho peer inspect -p <peer> -w <workspace>","dashboard: peer card + recent conclusions + configuration"),
("honcho peer representation -p <peer> -w <workspace>", "global peer representation"),
("honcho peer representation -p <peer> -w <workspace> -s <session>", "session-scoped peer representation"),
("honcho peer card -p <peer> -w <workspace>", "synthesized identity: traits, preferences, instructions"),
("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
]
option_rows = [
("-w / --workspace", "scope to a workspace"),
("-p / --peer", "scope to a peer"),
("-s / --session", "scope to a session"),
("--json", "force JSON output for scripts and agents"),
("--help", "show help for any command (e.g. honcho peer --help)"),
]
console.print(_welcome_panel("getting started", start_rows))
console.print(_welcome_panel("commands", cmd_rows))
console.print(_welcome_panel("memory", memory_rows))
console.print(_welcome_panel("options", option_rows))
console.print()
class HonchoTyperGroup(TyperGroup):
"""Typer group with pattern/example usage and top-level welcome.
Applied via ``cls=`` on every ``typer.Typer(...)`` in this package,
so no class-level monkey-patching is needed.
"""
def get_usage(self, ctx):
"""Replace Click's 'Usage: …' with pattern/example rows."""
original = click.Command.get_usage(self, ctx)
pattern = original.replace("Usage: ", "", 1) if original.startswith("Usage: ") else original
subs = self.list_commands(ctx)
example = f"{ctx.command_path} {subs[0]}" if subs else f"{ctx.command_path} --help"
return f"pattern: {pattern}\nexample: {example}"
def format_help(self, ctx, formatter):
"""Top-level --help renders the welcome; sub-groups fall through to Typer."""
if ctx.parent is None:
print_welcome(Console())
return
super().format_help(ctx, formatter)