feat(plugins): install plugins from private git repos using the user's stored credentials

`hermes plugins install owner/private-repo` failed for every private repository
even when the same user could `git clone` it from their shell: the hardened
`noninteractive_git_env` disables credential helpers, askpass and global config
(so a hostile repo can't make our plumbing prompt or hang), which also blocks
the user's own stored credential. The result was "could not read Username" or a
60s hang on a GUI askpass, with no hint about how to authenticate.

New `hermes_cli/git_credentials.py` resolves a credential up front from sources
the user already owns — GITHUB_TOKEN/GH_TOKEN (profile-scoped), `gh auth token`,
then `git credential fill` against their configured helpers with prompting
disabled (any host) — and hands it to git as a one-shot
`http.<origin>/.extraheader` via the GIT_CONFIG_* env block. Nothing lands in
the URL, `.git/config` or install metadata. The same path covers
`plugins update`, catalog MCP git installs and profile-distribution staging,
which share the same hardened env and the same failure.

A private-repo clone with no credential now fails fast with an actionable hint.
This commit is contained in:
Teknium
2026-09-09 16:49:23 -07:00
parent 6e07eb4838
commit 9886f6e53b
8 changed files with 247 additions and 18 deletions
+2 -1
View File
@@ -379,7 +379,8 @@ def _do_git_install(entry: CatalogEntry) -> Path:
# upfront so the fast path doesn't always fail noisily before the full-clone fallback.
is_sha_ref = bool(re.fullmatch(r"[0-9a-f]{7,40}", install.ref))
# Never hang on a credential prompt: installs run from CLI/dashboard flows nobody can answer.
_git_env = noninteractive_git_env()
from hermes_cli.git_credentials import with_git_auth
_git_env = with_git_auth(noninteractive_git_env(), install.url)
def _git(*args: str) -> int:
return subprocess.run([git, *args], stdin=subprocess.DEVNULL, env=_git_env).returncode