File size: 8,052 Bytes
5655a42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""``hermes plugins`` subcommand parser."""

from __future__ import annotations

from typing import Callable

from hermes_cli.subcommands._shared import add_json_flag


def build_plugins_parser(subparsers, *, cmd_plugins: Callable) -> None:
    """Attach the ``plugins`` subcommand to ``subparsers``."""
    plugins_parser = subparsers.add_parser(
        "plugins", help="Manage and validate plugins",
        description="Install, update, remove, list, or validate native Hermes plugins "
            "and portable Agent Plugins v1 packages. Portable packages install disabled.")
    plugins_subparsers = plugins_parser.add_subparsers(dest="plugins_action")

    plugins_install = plugins_subparsers.add_parser(
        "install", help="Install a plugin from the curated catalog, a Git URL, or owner/repo")
    plugins_install.add_argument(
        "identifier",
        help="Bare plugin catalog entry name (see `hermes plugins search`), Git URL, or owner/repo "
            "shorthand (e.g. anpicasso/hermes-plugin-chrome-profiles)")
    plugins_install.add_argument(
        "--force", "-f", action="store_true", help="Remove existing plugin and reinstall")
    plugins_install.add_argument(
        "--ref", metavar="COMMIT_SHA",
        help="Install exactly one immutable 40-character Git commit SHA")
    plugins_install.add_argument(
        "--allow-removed", action="store_true",
        help="DANGEROUS: bypass the catalog removed-plugin blocklist check")
    _install_enable_group = plugins_install.add_mutually_exclusive_group()
    _install_enable_group.add_argument(
        "--enable", action="store_true",
        help="Auto-enable the plugin after install (skip confirmation prompt)")
    _install_enable_group.add_argument(
        "--no-enable", action="store_true",
        help="Install disabled (skip confirmation prompt); enable later with `hermes plugins enable <name>`",
    )

    plugins_search = plugins_subparsers.add_parser(
        "search", help="Search the curated Hermes plugin catalog")
    plugins_search.add_argument(
        "term", nargs="?", default="",
        help="Query matched against entry names, descriptions and declared tools (omit to list the whole catalog)")
    add_json_flag(plugins_search, "Print machine-readable JSON")

    plugins_subparsers.add_parser("browse", help="List every curated plugin catalog entry")

    plugins_validate = plugins_subparsers.add_parser(
        "validate", help="Validate a plugin directory for catalog admission (CI gate)")
    plugins_validate.add_argument("path", help="Path to the plugin directory")
    add_json_flag(plugins_validate, "Print machine-readable JSON (for CI)")

    plugins_update = plugins_subparsers.add_parser(
        "update", help="Pull latest changes for an installed plugin")
    plugins_update.add_argument("name", help="Plugin name to update")

    plugins_remove = plugins_subparsers.add_parser(
        "remove", aliases=["rm", "uninstall"], help="Remove an installed plugin")
    plugins_remove.add_argument("name", help="Plugin directory name to remove")

    plugins_list = plugins_subparsers.add_parser(
        "list", aliases=["ls"], help="List installed plugins")
    plugins_list.add_argument("--enabled", action="store_true", help="Show only enabled plugins")
    plugins_list.add_argument(
        "--user", action="store_true",
        help="Show only user-installed plugins (including git plugins)")
    plugins_list.add_argument("--no-bundled", action="store_true", help="Hide bundled plugins")
    plugins_list.add_argument(
        "--plain", action="store_true",
        help="Print compact plain-text output instead of a Rich table")
    add_json_flag(plugins_list, "Print machine-readable JSON")

    plugins_enable = plugins_subparsers.add_parser("enable", help="Enable a disabled plugin")
    plugins_enable.add_argument("name", help="Plugin name to enable")
    _enable_override_group = plugins_enable.add_mutually_exclusive_group()
    _enable_override_group.add_argument(
        "--allow-tool-override", action="store_true",
        help="Grant this plugin permission to replace built-in tools "
        "(e.g. shell_exec, write_file). Skips the confirmation prompt.")
    _enable_override_group.add_argument(
        "--no-allow-tool-override", action="store_true",
        help="Enable without granting built-in tool override (skip prompt).")

    plugins_disable = plugins_subparsers.add_parser(
        "disable", help="Disable a plugin without removing it")
    plugins_disable.add_argument("name", help="Plugin name to disable")

    plugins_capabilities = plugins_subparsers.add_parser(
        "capabilities", help="Show declared vs granted capabilities per plugin",
        description="Show each plugin's declared capabilities (from plugin.yaml) "
            "against what the user has granted. Capabilities are a consent "
            "and audit layer over host API surfaces — NOT a sandbox.")
    plugins_capabilities.add_argument(
        "name", nargs="?", default=None,
        help="Plugin id to inspect (omit to list all plugins with capabilities)")

    plugins_doctor = plugins_subparsers.add_parser(
        "doctor", help="Validate a plugin with the real runtime contracts")
    plugins_doctor.add_argument(
        "target", nargs="?", default=".",
        help="Plugin path or installed plugin id (default: current directory)")
    plugins_doctor.add_argument(
        "--ci", action="store_true", help="Exit non-zero when validation reports an error")

    plugins_compat = plugins_subparsers.add_parser(
        "compat",
        help="Show installed plugins that import paths removed by the Sep 2026 decomposition",
        description="Statically scans every enabled external plugin for imports of pre-decomposition "
            "module paths (see COMPAT_MANIFEST.md) and prints file:line, old path -> new path. "
            "Exits 1 when any plugin is affected. Plugins still affected on the removal date are "
            "not loaded (override: plugins.allow_deprecated_imports: true).")
    plugins_compat.add_argument("--json", action="store_true", help="Machine-readable output")
    plugins_compat.add_argument(
        "path", nargs="?", help="Scan one plugin directory instead of the installed set (for plugin authors)")

    plugins_pack = plugins_subparsers.add_parser(
        "pack", help="Declarative, shareable plugin sets (hermes-pack.yaml)",
        description="Install, export, or inspect plugin packs — a single YAML file "
            "pinning a set of plugins to exact commit SHAs, with optional "
            "non-secret config seeds. Installing a pack fans out to ordinary "
            "pinned installs; capability consent stays per-plugin.")
    pack_subparsers = plugins_pack.add_subparsers(dest="pack_action")

    pack_install = pack_subparsers.add_parser(
        "install", help="Review and install a pack from a file path or https URL")
    pack_install.add_argument("source", help="Path to a hermes-pack.yaml file, or an https:// URL")
    pack_install.add_argument(
        "--force", "-f", action="store_true", help="Reinstall plugins that already exist")

    pack_export = pack_subparsers.add_parser(
        "export", help="Emit a pack YAML for the current install on stdout")
    pack_export.add_argument(
        "--enabled-only", action="store_true",
        help="Only include plugins currently in plugins.enabled")
    pack_export.add_argument(
        "--name", default="my-hermes-pack", help="Pack name to embed in the exported YAML")

    pack_show = pack_subparsers.add_parser(
        "show", help="Dry-run: parse and display a pack without installing")
    pack_show.add_argument("source", help="Path to a hermes-pack.yaml file, or an https:// URL")

    plugins_show = plugins_subparsers.add_parser(
        "show", aliases=["info"], help="Show details for a single plugin (including emits/listens)")
    plugins_show.add_argument("name", help="Plugin name or key to show")

    plugins_parser.set_defaults(func=cmd_plugins)