mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
Subcommands whose handler was a closure defined inside main() — memory, acp, tools, insights, skills, pairing, plugins, mcp, claw — have their handler promoted to a top-level function and their parser block extracted into hermes_cli/subcommands/<name>.py (build_<name>_parser, injected handler). These 9 had zero closure-over-main-locals, so promotion is a pure relocation. acp/mcp parser blocks use the shared add_accept_hooks_flag helper. main() 1798 -> 954 LOC (71% below the 3297 Phase-2 starting point); add_parser calls in main.py 89 -> 28. Deferred: sessions, computer-use, secrets handlers reference <name>_parser (for a no-subcommand print_help fallback) — left in place to avoid the _self_parser indirection; minority, low value. Behavior-neutral: all 9 subcommands' --help (incl nested subactions) byte- identical to pre-extraction (diff-verified). tests/hermes_cli/ 6519 passed / 0 failed; new test_subcommands_followup.py covers the 9 builders.
269 lines
9.6 KiB
Python
269 lines
9.6 KiB
Python
"""``hermes skills`` subcommand parser.
|
|
|
|
Extracted from ``hermes_cli/main.py:main()`` (god-file Phase 2 follow-up).
|
|
Handler injected to avoid importing ``main``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Callable
|
|
|
|
|
|
def build_skills_parser(subparsers, *, cmd_skills: Callable) -> None:
|
|
"""Attach the ``skills`` subcommand to ``subparsers``."""
|
|
skills_parser = subparsers.add_parser(
|
|
"skills",
|
|
help="Search, install, configure, and manage skills",
|
|
description="Search, install, inspect, audit, configure, and manage skills from skills.sh, well-known agent skill endpoints, GitHub, ClawHub, and other registries.",
|
|
)
|
|
skills_subparsers = skills_parser.add_subparsers(dest="skills_action")
|
|
|
|
skills_browse = skills_subparsers.add_parser(
|
|
"browse", help="Browse all available skills (paginated)"
|
|
)
|
|
skills_browse.add_argument(
|
|
"--page", type=int, default=1, help="Page number (default: 1)"
|
|
)
|
|
skills_browse.add_argument(
|
|
"--size", type=int, default=20, help="Results per page (default: 20)"
|
|
)
|
|
skills_browse.add_argument(
|
|
"--source",
|
|
default="all",
|
|
choices=[
|
|
"all",
|
|
"official",
|
|
"skills-sh",
|
|
"well-known",
|
|
"github",
|
|
"clawhub",
|
|
"lobehub",
|
|
"browse-sh",
|
|
],
|
|
help="Filter by source (default: all)",
|
|
)
|
|
|
|
skills_search = skills_subparsers.add_parser(
|
|
"search", help="Search skill registries"
|
|
)
|
|
skills_search.add_argument("query", help="Search query")
|
|
skills_search.add_argument(
|
|
"--source",
|
|
default="all",
|
|
choices=[
|
|
"all",
|
|
"official",
|
|
"skills-sh",
|
|
"well-known",
|
|
"github",
|
|
"clawhub",
|
|
"lobehub",
|
|
"browse-sh",
|
|
],
|
|
)
|
|
skills_search.add_argument("--limit", type=int, default=10, help="Max results")
|
|
skills_search.add_argument(
|
|
"--json",
|
|
action="store_true",
|
|
help="Output JSON instead of a table (full identifiers, scripting-friendly)",
|
|
)
|
|
|
|
skills_install = skills_subparsers.add_parser("install", help="Install a skill")
|
|
skills_install.add_argument(
|
|
"identifier",
|
|
help="Skill identifier (e.g. openai/skills/skill-creator) or a direct HTTP(S) URL to a SKILL.md file",
|
|
)
|
|
skills_install.add_argument(
|
|
"--category", default="", help="Category folder to install into"
|
|
)
|
|
skills_install.add_argument(
|
|
"--name",
|
|
default="",
|
|
help="Override the skill name (useful when installing from a URL whose SKILL.md has no `name:` frontmatter)",
|
|
)
|
|
skills_install.add_argument(
|
|
"--force", action="store_true", help="Install despite blocked scan verdict"
|
|
)
|
|
skills_install.add_argument(
|
|
"--yes",
|
|
"-y",
|
|
action="store_true",
|
|
help="Skip confirmation prompt (needed in TUI mode)",
|
|
)
|
|
|
|
skills_inspect = skills_subparsers.add_parser(
|
|
"inspect", help="Preview a skill without installing"
|
|
)
|
|
skills_inspect.add_argument("identifier", help="Skill identifier")
|
|
|
|
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
|
|
skills_list.add_argument(
|
|
"--source", default="all", choices=["all", "hub", "builtin", "local"]
|
|
)
|
|
skills_list.add_argument(
|
|
"--enabled-only",
|
|
action="store_true",
|
|
help="Hide disabled skills. Use with -p <profile> to see exactly "
|
|
"which skills will load for that profile.",
|
|
)
|
|
|
|
skills_check = skills_subparsers.add_parser(
|
|
"check", help="Check installed hub skills for updates"
|
|
)
|
|
skills_check.add_argument(
|
|
"name", nargs="?", help="Specific skill to check (default: all)"
|
|
)
|
|
|
|
skills_update = skills_subparsers.add_parser(
|
|
"update", help="Update installed hub skills"
|
|
)
|
|
skills_update.add_argument(
|
|
"name",
|
|
nargs="?",
|
|
help="Specific skill to update (default: all outdated skills)",
|
|
)
|
|
|
|
skills_audit = skills_subparsers.add_parser(
|
|
"audit", help="Re-scan installed hub skills"
|
|
)
|
|
skills_audit.add_argument(
|
|
"name", nargs="?", help="Specific skill to audit (default: all)"
|
|
)
|
|
skills_audit.add_argument(
|
|
"--deep",
|
|
action="store_true",
|
|
help="Run AST-level analysis on Python files (opt-in diagnostic)",
|
|
)
|
|
|
|
skills_uninstall = skills_subparsers.add_parser(
|
|
"uninstall", help="Remove a hub-installed skill"
|
|
)
|
|
skills_uninstall.add_argument("name", help="Skill name to remove")
|
|
|
|
skills_reset = skills_subparsers.add_parser(
|
|
"reset",
|
|
help="Reset a bundled skill — clears 'user-modified' tracking so updates work again",
|
|
description=(
|
|
"Clear a bundled skill's entry from the sync manifest (~/.hermes/skills/.bundled_manifest) "
|
|
"so future 'hermes update' runs stop marking it as user-modified. Pass --restore to also "
|
|
"replace the current copy with the bundled version."
|
|
),
|
|
)
|
|
skills_reset.add_argument(
|
|
"name", help="Skill name to reset (e.g. google-workspace)"
|
|
)
|
|
skills_reset.add_argument(
|
|
"--restore",
|
|
action="store_true",
|
|
help="Also delete the current copy and re-copy the bundled version",
|
|
)
|
|
skills_reset.add_argument(
|
|
"--yes",
|
|
"-y",
|
|
action="store_true",
|
|
help="Skip confirmation prompt when using --restore",
|
|
)
|
|
|
|
skills_opt_out = skills_subparsers.add_parser(
|
|
"opt-out",
|
|
help="Stop bundled skills from being seeded into this profile",
|
|
description=(
|
|
"Write the .no-bundled-skills marker so the installer, "
|
|
"`hermes update`, and any direct sync stop seeding bundled skills "
|
|
"into the active profile. By default nothing already on disk is "
|
|
"touched. Pass --remove to ALSO delete bundled skills that are "
|
|
"unmodified (user-edited and hub/local skills are never removed)."
|
|
),
|
|
)
|
|
skills_opt_out.add_argument(
|
|
"--remove",
|
|
action="store_true",
|
|
help="Also delete already-present unmodified bundled skills",
|
|
)
|
|
skills_opt_out.add_argument(
|
|
"--yes",
|
|
"-y",
|
|
action="store_true",
|
|
help="Skip confirmation prompt when using --remove",
|
|
)
|
|
|
|
skills_opt_in = skills_subparsers.add_parser(
|
|
"opt-in",
|
|
help="Re-enable bundled-skill seeding (undo opt-out)",
|
|
description=(
|
|
"Remove the .no-bundled-skills marker so bundled skills are seeded "
|
|
"again on the next `hermes update`. Pass --sync to re-seed now."
|
|
),
|
|
)
|
|
skills_opt_in.add_argument(
|
|
"--sync",
|
|
action="store_true",
|
|
help="Re-seed bundled skills immediately instead of waiting for update",
|
|
)
|
|
|
|
skills_repair_official = skills_subparsers.add_parser(
|
|
"repair-official",
|
|
help="Backfill or restore official optional skills from repo source",
|
|
description=(
|
|
"Repair official optional skill provenance. By default, only backfills "
|
|
"hub metadata for exact matches. Pass --restore to replace missing or "
|
|
"mutated active copies from optional-skills/, moving existing copies to "
|
|
"a restore backup first. Use name 'all' to repair every optional skill."
|
|
),
|
|
)
|
|
skills_repair_official.add_argument(
|
|
"name", help="Official optional skill folder/frontmatter name, or 'all'"
|
|
)
|
|
skills_repair_official.add_argument(
|
|
"--restore",
|
|
action="store_true",
|
|
help="Restore from official optional source, backing up existing matching copies",
|
|
)
|
|
skills_repair_official.add_argument(
|
|
"--yes",
|
|
"-y",
|
|
action="store_true",
|
|
help="Skip confirmation prompt when using --restore",
|
|
)
|
|
|
|
skills_publish = skills_subparsers.add_parser(
|
|
"publish", help="Publish a skill to a registry"
|
|
)
|
|
skills_publish.add_argument("skill_path", help="Path to skill directory")
|
|
skills_publish.add_argument(
|
|
"--to", default="github", choices=["github", "clawhub"], help="Target registry"
|
|
)
|
|
skills_publish.add_argument(
|
|
"--repo", default="", help="Target GitHub repo (e.g. openai/skills)"
|
|
)
|
|
|
|
skills_snapshot = skills_subparsers.add_parser(
|
|
"snapshot", help="Export/import skill configurations"
|
|
)
|
|
snapshot_subparsers = skills_snapshot.add_subparsers(dest="snapshot_action")
|
|
snap_export = snapshot_subparsers.add_parser(
|
|
"export", help="Export installed skills to a file"
|
|
)
|
|
snap_export.add_argument("output", help="Output JSON file path (use - for stdout)")
|
|
snap_import = snapshot_subparsers.add_parser(
|
|
"import", help="Import and install skills from a file"
|
|
)
|
|
snap_import.add_argument("input", help="Input JSON file path")
|
|
snap_import.add_argument(
|
|
"--force", action="store_true", help="Force install despite caution verdict"
|
|
)
|
|
|
|
skills_tap = skills_subparsers.add_parser("tap", help="Manage skill sources")
|
|
tap_subparsers = skills_tap.add_subparsers(dest="tap_action")
|
|
tap_subparsers.add_parser("list", help="List configured taps")
|
|
tap_add = tap_subparsers.add_parser("add", help="Add a GitHub repo as skill source")
|
|
tap_add.add_argument("repo", help="GitHub repo (e.g. owner/repo)")
|
|
tap_rm = tap_subparsers.add_parser("remove", help="Remove a tap")
|
|
tap_rm.add_argument("name", help="Tap name to remove")
|
|
|
|
# config sub-action: interactive enable/disable
|
|
skills_subparsers.add_parser(
|
|
"config",
|
|
help="Interactive skill configuration — enable/disable individual skills",
|
|
)
|
|
skills_parser.set_defaults(func=cmd_skills)
|