fix(cli): seed bundled skills on dashboard + gateway entrypoints

`sync_skills(quiet=True)` was only being called from inside `cmd_chat`,
which meant `hermes dashboard` (the desktop GUI's backend) and `hermes
gateway` (Telegram/Discord/Slack/etc daemons) never seeded the bundled
skill library into ~/.hermes/skills/.

This surfaced as "No skills found" in the desktop GUI's skills panel on
fresh installs, despite the agent having access to the full bundled
library when invoked via `hermes chat`. scripts/install.ps1 worked
around it by running skills_sync.py as part of Copy-ConfigTemplates,
but that's not part of the desktop installer's bootstrap chain.

Fix
- Extract the skills-sync block from cmd_chat into a module-level
  `_sync_bundled_skills_quietly()` helper.
- Call the helper from cmd_chat (preserving existing behavior),
  cmd_dashboard (after the --status/--stop early-return paths and
  fastapi import check, so we don't run skills_sync on management
  commands or when deps aren't installed), and cmd_gateway.

Why these three entrypoints
- cmd_chat: the user's primary CLI entrypoint
- cmd_dashboard: the desktop GUI's backend; this is what `hermes
  dashboard --tui` invokes when the desktop bootstrapper spawns Hermes
- cmd_gateway: long-running daemons where the user expects the agent
  to have full skill access

Other entrypoints (cmd_config, cmd_doctor, cmd_login, cmd_status,
etc.) are management commands that don't need skill discovery and were
never running skills_sync in the first place — leaving them alone.

Idempotence
- tools/skills_sync.py is manifest-based: skipped skills cost
  milliseconds. Calling it from multiple entrypoints adds no real
  cost, and users running `hermes chat` then `hermes dashboard` get
  two fast no-ops on the second call.

Failure handling
- Helper wraps skills_sync in try/except. Skills are an enhancement,
  not a hard dependency — Hermes runs fine with an empty skills/ dir.

Files
- hermes_cli/main.py:
  + new helper `_sync_bundled_skills_quietly()` at module level
  + cmd_chat: replace inline block with helper call
  + cmd_dashboard: add helper call after fastapi import succeeds
  + cmd_gateway: add helper call before delegating to gateway_command
This commit is contained in:
emozilla 2026-05-11 15:53:50 -04:00
parent 50a9d6333f
commit 4b3839a8ee

View file

@ -1342,6 +1342,26 @@ def _pin_kanban_board_env() -> None:
pass
def _sync_bundled_skills_quietly() -> None:
"""Seed ``~/.hermes/skills/`` with the bundled skill library on first launch.
Called from any CLI entrypoint that the user might use as their first
interaction with Hermes chat, dashboard (the desktop GUI's backend),
and gateway. The skills_sync module is manifest-based and idempotent:
skipped skills cost ~milliseconds, so calling this repeatedly is fine.
Failures are swallowed because skills are an enhancement, not a hard
dependency. Hermes still functions without them; the user just sees an
empty skills library.
"""
try:
from tools.skills_sync import sync_skills
sync_skills(quiet=True)
except Exception:
pass
def cmd_chat(args):
"""Run interactive chat CLI."""
use_tui = getattr(args, "tui", False) or os.environ.get("HERMES_TUI") == "1"
@ -1421,12 +1441,7 @@ def cmd_chat(args):
pass
# Sync bundled skills on every CLI launch (fast -- skips unchanged skills)
try:
from tools.skills_sync import sync_skills
sync_skills(quiet=True)
except Exception:
pass
_sync_bundled_skills_quietly()
# --yolo: bypass all dangerous command approvals
if getattr(args, "yolo", False):
@ -1504,6 +1519,8 @@ def cmd_chat(args):
def cmd_gateway(args):
"""Gateway management commands."""
_sync_bundled_skills_quietly()
from hermes_cli.gateway import gateway_command
gateway_command(args)
@ -8973,6 +8990,12 @@ def cmd_dashboard(args):
print(f"Import error: {e}")
sys.exit(1)
# Seed bundled skills on first dashboard launch so the desktop GUI's
# skills picker / agent skill discovery sees the bundled library.
# cmd_chat does this in its own pre-dispatch block; the dashboard
# backend is the desktop's primary entrypoint and needs the same.
_sync_bundled_skills_quietly()
if "HERMES_WEB_DIST" not in os.environ:
if not _build_web_ui(PROJECT_ROOT / "apps" / "dashboard", fatal=True):
sys.exit(1)