Files
hermes-agent/hermes_cli/gateway_chat_listing.py
Teknium 7b4a2c6ba1 fix(cli): bare -c, --resume latest, --list-tools(ets) and the resume footer on the gateway path
`hermes chat` refused bare -c, --resume latest, --list-tools and --list-toolsets (exit 2), and
`chat -q` dropped main's "Resume this session with: hermes --resume <id>" footer, which scripts
parse.

- bare -c: this terminal's breadcrumb session (main's rule) when the owner still has it, else
  the owner's most recent CLI session; --resume latest: the most recent CLI session. Both are
  resolved by the owner (session.resume latest='cli' [workspace]) with main's
  _resolve_last_session rule -- cli+oneshot sources, this workspace (git root / cwd) first,
  then global -- so the client still opens no database. Another principal's sessions are
  skipped, never confirmed. Interactive launches write the terminal breadcrumb again.
- --list-tools / --list-toolsets: print the catalog for this profile's CLI selection (or
  --toolsets) and exit, without a session or a gateway, as main did.
- chat -q without -Q: the single-query exit block again (with -p for a named profile).
- cli.main docstring: drop the --image / -w / --skills examples that exit 2 now.

-w, --image, --run-budget, -v, --compact stay documented refusals (launcher-side worktree,
attachment staging, per-launch budget, display-only flags).

Review: https://github.com/NousResearch/hermes-agent/pull/106742#issuecomment-6073624820
(cherry picked from commit 80c6d9564cf301bc25c75a40ecab3e75c1f63b84)
2026-10-09 02:10:48 -07:00

55 lines
2.5 KiB
Python

"""``--list-tools`` / ``--list-toolsets`` on the gateway path: a catalog listing needs no session.
Main built a whole in-process CLI to print these and exit; the listing reads only the registry
and this profile's CLI toolset selection, so it runs in the client without an agent or a gateway.
"""
def _selection(args):
from hermes_cli.config import load_config
from hermes_cli.tools_config import _get_platform_tools
requested = getattr(args, "toolsets", None)
if isinstance(requested, (list, tuple)):
requested = ",".join(map(str, requested))
if isinstance(requested, str) and requested.strip():
return sorted({name.strip() for name in requested.split(",") if name.strip()}), []
config = load_config()
disabled = (config.get("agent") or {}).get("disabled_toolsets") or []
return sorted(_get_platform_tools(config, "cli")), list(disabled)
def print_tool_listing(args) -> int:
from agent.i18n import t
enabled, disabled = _selection(args)
if getattr(args, "list_tools", False):
from model_tools import get_tool_definitions, get_toolset_for_tool
# Pre-assembly list, as main's /tools: deferred tool_search tools are shown too.
tools = get_tool_definitions(enabled_toolsets=enabled, disabled_toolsets=disabled, quiet_mode=True,
skip_tool_search_assembly=True)
if not tools:
print(t("cli.tools.none_available"))
return 0
grouped = {}
for tool in sorted(tools, key=lambda item: item["function"]["name"]):
name = tool["function"]["name"]
desc = tool["function"].get("description", "").split("\n")[0]
if ". " in desc:
desc = desc[:desc.index(". ") + 1]
grouped.setdefault(get_toolset_for_tool(name) or "unknown", []).append((name, desc))
print(t("cli.tools.header"))
for toolset in sorted(grouped):
print(f"\n [{toolset}]")
for name, desc in grouped[toolset]:
print(f" * {name:<20} - {desc}")
print(f"\n {t('cli.tools.total', count=str(len(tools)))}")
return 0
from toolsets import get_all_toolsets, get_toolset_info
print(t("cli.toolsets.header"))
for name in sorted(get_all_toolsets()):
info = get_toolset_info(name)
if info:
marker = "(*)" if name in enabled else " "
print(f" {marker} {name:<18} [{info['tool_count']:>2} tools] - {info['description']}")
print(f"\n {t('cli.toolsets.currently_enabled_legend')}")
return 0