feat(plugins): install from a subdirectory within a repo (#42963)

Support installing a plugin that lives in a subdirectory of a larger
repo (docs/tests at root, plugin in a subdir) without forcing a
dedicated single-plugin repo.

Identifier syntax:
  owner/repo/path/to/plugin        (shorthand + subpath)
  <url>.git/path/to/plugin         (.git boundary on GitHub-style URLs)
  <url>#path/to/plugin             (explicit fragment, any scheme)

_resolve_git_url now returns (git_url, subdir); _install_plugin_core
reads the manifest from and moves only the subdir, so root-level docs
and tests no longer leak into ~/.hermes/plugins. _resolve_subdir_within
guards against path traversal, missing dirs, and non-directories.

Both the CLI (hermes plugins install) and the dashboard install endpoint
inherit this for free since they share _install_plugin_core. Dashboard
install hint + placeholder updated to advertise the subdir syntax.

Co-authored-by: Austin Pickett <pickett.austin@gmail.com>
This commit is contained in:
Teknium
2026-06-09 13:42:51 -04:00
committed by GitHub
co-authored by Austin Pickett
parent ff9c110d5a
commit f6f573ebaa
4 changed files with 276 additions and 28 deletions
+1 -1
View File
@@ -362,7 +362,7 @@ export const en: Translations = {
inactive: "inactive",
installBtn: "Install",
installHeading: "Install from GitHub / Git URL",
installHint: "Use owner/repo shorthand or a full https:// or git@ clone URL.",
installHint: "Use owner/repo shorthand or a full https:// or git@ clone URL. For a plugin in a subdirectory, append the path: owner/repo/path/to/plugin (or <url>#path/to/plugin).",
memoryProviderLabel: "Memory provider",
missingEnvWarn: "Set these in Keys before the plugin can run:",
noDashboardTab: "No dashboard tab",
+1 -1
View File
@@ -240,7 +240,7 @@ export default function PluginsPage() {
<Input
className="font-mono-ui lowercase"
id="install-url"
placeholder="owner/repo or https://..."
placeholder="owner/repo, owner/repo/subdir, or https://..."
spellCheck={false}
value={installId}
onChange={(e) => setInstallId(e.target.value)}