mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(skills): parse stored GitHub credentials without scanner false positives
Co-authored-by: Syed Annas <28944679+AnnasMazhar@users.noreply.github.com> Co-authored-by: Bryan Neva <13835061+bryanneva@users.noreply.github.com>
This commit is contained in:
parent
4854961d74
commit
9b97dea1e6
24 changed files with 251 additions and 27 deletions
|
|
@ -50,7 +50,7 @@ EXCLUDED_SKILL_DIRS = frozenset(
|
|||
SKILL_SUPPORT_DIRS = frozenset(("references", "templates", "assets", "scripts"))
|
||||
|
||||
|
||||
def is_excluded_skill_path(path) -> bool:
|
||||
def is_excluded_skill_path(path, *, root: Optional[Path] = None) -> bool:
|
||||
"""True if *path* should be skipped by active skill scanners.
|
||||
|
||||
Use this on every ``SKILL.md`` path produced by direct ``rglob`` scans to
|
||||
|
|
@ -66,11 +66,11 @@ def is_excluded_skill_path(path) -> bool:
|
|||
from pathlib import PurePath
|
||||
parts = PurePath(str(path)).parts
|
||||
return any(part in EXCLUDED_SKILL_DIRS for part in parts) or is_skill_support_path(
|
||||
path
|
||||
path, root=root
|
||||
)
|
||||
|
||||
|
||||
def is_skill_support_path(path) -> bool:
|
||||
def is_skill_support_path(path, *, root: Optional[Path] = None) -> bool:
|
||||
"""True if *path* is under a support dir of an actual skill root.
|
||||
|
||||
``references/``, ``templates/``, ``assets/``, and ``scripts/`` are
|
||||
|
|
@ -92,6 +92,8 @@ def is_skill_support_path(path) -> bool:
|
|||
if part not in SKILL_SUPPORT_DIRS or idx == 0:
|
||||
continue
|
||||
skill_root = Path(*parts[:idx])
|
||||
if root is not None and not path_obj.is_absolute():
|
||||
skill_root = root / skill_root
|
||||
if (skill_root / "SKILL.md").exists():
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ If git credentials are already configured (via credential.helper store), the tok
|
|||
|
||||
```bash
|
||||
# Read from git credential store
|
||||
grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|'
|
||||
uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py"
|
||||
```
|
||||
|
||||
### Helper: Detect Auth Method
|
||||
|
|
@ -224,7 +224,7 @@ elif _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] &&
|
|||
export GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
echo "AUTH_METHOD=curl"
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
export GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
export GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
echo "AUTH_METHOD=curl"
|
||||
else
|
||||
echo "AUTH_METHOD=none"
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ elif _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] &&
|
|||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
GH_AUTH_METHOD="curl"
|
||||
fi
|
||||
elif [ -f "$HOME/.git-credentials" ] && grep -q "github.com" "$HOME/.git-credentials" 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" "$HOME/.git-credentials" | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
elif [ -f "$HOME/.git-credentials" ]; then
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
GH_AUTH_METHOD="curl"
|
||||
fi
|
||||
|
|
|
|||
65
skills/github/github-auth/scripts/git-credential-token.py
Normal file
65
skills/github/github-auth/scripts/git-credential-token.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Print the first unambiguous GitHub token in a git credential-store file."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
|
||||
_TOKEN_PREFIXES = ("ghp_", "github_pat_", "gho_", "ghu_", "ghs_", "ghr_")
|
||||
_INVALID_ESCAPE = re.compile(r"%(?![0-9A-Fa-f]{2})")
|
||||
|
||||
|
||||
def _decode(value: str | None) -> str:
|
||||
if value is None or _INVALID_ESCAPE.search(value):
|
||||
return ""
|
||||
decoded = unquote(value)
|
||||
if not decoded or any(ord(char) <= 0x1F or 0x7F <= ord(char) <= 0x9F for char in decoded):
|
||||
return ""
|
||||
return decoded
|
||||
|
||||
|
||||
def _token_from_url(line: str) -> str:
|
||||
if "\r" in line or "\n" in line:
|
||||
return ""
|
||||
try:
|
||||
credential = urlsplit(line)
|
||||
port = credential.port
|
||||
except ValueError:
|
||||
return ""
|
||||
if credential.scheme != "https" or credential.hostname != "github.com" or port not in (None, 443):
|
||||
return ""
|
||||
|
||||
username = _decode(credential.username)
|
||||
password = _decode(credential.password)
|
||||
if not username:
|
||||
return ""
|
||||
if password and password != "x-oauth-basic":
|
||||
return password
|
||||
if password == "x-oauth-basic":
|
||||
return username
|
||||
return username if username.startswith(_TOKEN_PREFIXES) else ""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
path = Path(sys.argv[1]).expanduser() if len(sys.argv) > 1 else Path.home() / ".git-credentials"
|
||||
try:
|
||||
lines = path.read_bytes().split(b"\n")
|
||||
except OSError:
|
||||
return 1
|
||||
|
||||
for raw_line in lines:
|
||||
try:
|
||||
line = raw_line.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
token = _token_from_url(line)
|
||||
if token:
|
||||
print(token)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -31,7 +31,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -241,6 +241,22 @@ def test_iter_skill_index_files_keeps_support_named_categories(tmp_path):
|
|||
assert is_excluded_skill_path(scripts_skill / "SKILL.md") is False
|
||||
|
||||
|
||||
def test_skill_support_path_uses_explicit_discovery_root_not_cwd(tmp_path, monkeypatch):
|
||||
discovery_root = tmp_path / "site-packages" / "skills"
|
||||
umbrella = discovery_root / "category" / "umbrella"
|
||||
nested = umbrella / "references" / "archived" / "SKILL.md"
|
||||
nested.parent.mkdir(parents=True)
|
||||
(umbrella / "SKILL.md").write_text("---\nname: umbrella\n---\n", encoding="utf-8")
|
||||
nested.write_text("---\nname: archived\n---\n", encoding="utf-8")
|
||||
elsewhere = tmp_path / "elsewhere"
|
||||
elsewhere.mkdir()
|
||||
monkeypatch.chdir(elsewhere)
|
||||
|
||||
relative = nested.relative_to(discovery_root)
|
||||
assert is_skill_support_path(relative, root=discovery_root) is True
|
||||
assert is_excluded_skill_path(relative, root=discovery_root) is True
|
||||
|
||||
|
||||
# ── skill_matches_platform on Termux ──────────────────────────────────────
|
||||
|
||||
|
||||
|
|
|
|||
101
tests/skills/test_github_credential_token.py
Normal file
101
tests/skills/test_github_credential_token.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""Regression tests for Tirith-safe GitHub credential extraction (#22722)."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
HELPER = REPO_ROOT / "skills/github/github-auth/scripts/git-credential-token.py"
|
||||
LEGACY_SED = r"sed 's|https://[^:]*:\([^@]*\)@.*|\1|'"
|
||||
SHIPPED_TREES = (
|
||||
REPO_ROOT / "skills/github",
|
||||
REPO_ROOT / "website/docs/user-guide/skills/bundled/github",
|
||||
REPO_ROOT
|
||||
/ "website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github",
|
||||
)
|
||||
|
||||
|
||||
def _extract(path: Path) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(HELPER), str(path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _credential_file(tmp_path: Path, value: str) -> Path:
|
||||
credentials = tmp_path / "credentials"
|
||||
credentials.write_text(value, encoding="utf-8", newline="")
|
||||
return credentials
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("credential", "token"),
|
||||
[
|
||||
("https://octocat:password-form-token@github.com\n", "password-form-token"),
|
||||
("https://oauth-token:x-oauth-basic@github.com\n", "oauth-token"),
|
||||
("https://ghp_token_only@github.com\n", "ghp_token_only"),
|
||||
("https://github_pat_token_only@github.com\n", "github_pat_token_only"),
|
||||
],
|
||||
)
|
||||
def test_extracts_supported_git_credential_url_forms(tmp_path, credential, token):
|
||||
result = _extract(_credential_file(tmp_path, credential))
|
||||
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == f"{token}\n"
|
||||
assert result.stderr == ""
|
||||
|
||||
|
||||
def test_extracts_password_from_exact_github_https_credential(tmp_path):
|
||||
credentials = _credential_file(
|
||||
tmp_path,
|
||||
"https://ignored:wrong@example.com\n"
|
||||
"https://octocat:secret%2Ftoken@github.com\n",
|
||||
)
|
||||
|
||||
result = _extract(credentials)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert result.stdout == "secret/token\n"
|
||||
assert result.stderr == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"credential",
|
||||
[
|
||||
"https://octocat:stolen@github.com.attacker.example\n",
|
||||
"https://octocat@github.com\n",
|
||||
"https://%6fctocat@github.com\n",
|
||||
"https://octocat:token@github.com%2eattacker.example\n",
|
||||
"https://octocat:token%0D%0AX-Injected%3Ayes@github.com\n",
|
||||
"https://ghp_token%0Ainjected@github.com\n",
|
||||
"https://octocat:token%00suffix@github.com\n",
|
||||
"https://octocat:token%09suffix@github.com\n",
|
||||
"https://octocat:token%C2%85suffix@github.com\n",
|
||||
"https://ghp_token%1Fsuffix@github.com\n",
|
||||
"https://ghp_token%C2%9Fsuffix@github.com\n",
|
||||
"https://octocat:bad%ZZtoken@github.com\n",
|
||||
"https://octocat:token@github.com:bogus\n",
|
||||
"http://octocat:token@github.com\n",
|
||||
],
|
||||
)
|
||||
def test_rejects_ambiguous_lookalike_or_malformed_credentials(tmp_path, credential):
|
||||
result = _extract(_credential_file(tmp_path, credential))
|
||||
|
||||
assert result.returncode == 1
|
||||
assert result.stdout == ""
|
||||
assert result.stderr == ""
|
||||
|
||||
|
||||
def test_bundled_github_skills_and_docs_do_not_ship_legacy_sed_url_regex():
|
||||
offenders = []
|
||||
for tree in SHIPPED_TREES:
|
||||
for path in tree.rglob("*"):
|
||||
if path.suffix in {".md", ".sh", ".py"} and LEGACY_SED in path.read_text(encoding="utf-8"):
|
||||
offenders.append(str(path.relative_to(REPO_ROOT)))
|
||||
|
||||
assert offenders == []
|
||||
|
|
@ -1848,6 +1848,24 @@ class TestOptionalSkillSourceMetadata:
|
|||
assert meta.repo == "NousResearch/hermes-agent"
|
||||
assert meta.path == "optional-skills/finance/3-statement-model"
|
||||
|
||||
def test_scan_all_accepts_install_prefix_but_rejects_nested_support_skills(self, tmp_path):
|
||||
optional_root = tmp_path / "venv" / "lib" / "site-packages" / "optional-skills"
|
||||
real = optional_root / "research" / "real-skill"
|
||||
nested = real / "references" / "archived-skill"
|
||||
nested.mkdir(parents=True)
|
||||
(real / "SKILL.md").write_text(
|
||||
"---\nname: real-skill\ndescription: real\n---\n", encoding="utf-8"
|
||||
)
|
||||
(nested / "SKILL.md").write_text(
|
||||
"---\nname: archived-skill\ndescription: nested\n---\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
src = OptionalSkillSource()
|
||||
src._optional_dir = optional_root
|
||||
|
||||
assert [meta.name for meta in src._scan_all()] == ["real-skill"]
|
||||
assert src._find_skill_dir("archived-skill") is None
|
||||
|
||||
|
||||
class TestOptionalSkillSourceBinaryAssets:
|
||||
def test_fetch_preserves_binary_assets(self, tmp_path):
|
||||
|
|
|
|||
|
|
@ -131,6 +131,16 @@ class TestDiscoverBundledSkills:
|
|||
skills = _discover_bundled_skills(tmp_path)
|
||||
assert len(skills) == 0
|
||||
|
||||
@pytest.mark.parametrize("support_dir", ["references", "scripts", "templates", "assets"])
|
||||
def test_ignores_nested_skill_packages_in_support_dirs(self, tmp_path, support_dir):
|
||||
real = tmp_path / "category" / "umbrella"
|
||||
nested = real / support_dir / "archived-skill"
|
||||
nested.mkdir(parents=True)
|
||||
(real / "SKILL.md").write_text("---\nname: umbrella\n---\n")
|
||||
(nested / "SKILL.md").write_text("---\nname: archived-skill\n---\n")
|
||||
|
||||
assert [name for name, _ in _discover_bundled_skills(tmp_path)] == ["umbrella"]
|
||||
|
||||
def test_nonexistent_dir_returns_empty(self, tmp_path):
|
||||
skills = _discover_bundled_skills(tmp_path / "nonexistent")
|
||||
assert skills == []
|
||||
|
|
|
|||
|
|
@ -3277,7 +3277,9 @@ class OptionalSkillSource(SkillSource):
|
|||
if not self._optional_dir.is_dir():
|
||||
return None
|
||||
for skill_md in self._optional_dir.rglob("SKILL.md"):
|
||||
if is_excluded_skill_path(skill_md):
|
||||
if is_excluded_skill_path(
|
||||
skill_md.relative_to(self._optional_dir), root=self._optional_dir
|
||||
):
|
||||
continue
|
||||
if skill_md.parent.name == name:
|
||||
return skill_md.parent
|
||||
|
|
@ -3290,7 +3292,9 @@ class OptionalSkillSource(SkillSource):
|
|||
|
||||
results: List[SkillMeta] = []
|
||||
for skill_md in sorted(self._optional_dir.rglob("SKILL.md")):
|
||||
if is_excluded_skill_path(skill_md):
|
||||
if is_excluded_skill_path(
|
||||
skill_md.relative_to(self._optional_dir), root=self._optional_dir
|
||||
):
|
||||
continue
|
||||
parent = skill_md.parent
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,13 @@ def _discover_bundled_skills(bundled_dir: Path) -> List[Tuple[str, Path]]:
|
|||
return skills
|
||||
|
||||
for skill_md in bundled_dir.rglob("SKILL.md"):
|
||||
if is_excluded_skill_path(skill_md):
|
||||
# Exclusions apply inside the bundled tree. The install prefix itself
|
||||
# may legitimately contain names such as ``venv`` or ``site-packages``;
|
||||
# treating those parent components as skill content makes every wheel
|
||||
# install discover zero bundled skills.
|
||||
if is_excluded_skill_path(
|
||||
skill_md.relative_to(bundled_dir), root=bundled_dir
|
||||
):
|
||||
continue
|
||||
skill_dir = skill_md.parent
|
||||
skill_name = _read_skill_name(skill_md, skill_dir.name)
|
||||
|
|
@ -302,7 +308,9 @@ def _optional_skill_index() -> Dict[str, Tuple[str, str, Path]]:
|
|||
if not optional_dir.exists():
|
||||
return index
|
||||
for skill_md in sorted(optional_dir.rglob("SKILL.md")):
|
||||
if is_excluded_skill_path(skill_md):
|
||||
if is_excluded_skill_path(
|
||||
skill_md.relative_to(optional_dir), root=optional_dir
|
||||
):
|
||||
continue
|
||||
src = skill_md.parent
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ If git credentials are already configured (via credential.helper store), the tok
|
|||
|
||||
```bash
|
||||
# Read from git credential store
|
||||
grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|'
|
||||
uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py"
|
||||
```
|
||||
|
||||
### Helper: Detect Auth Method
|
||||
|
|
@ -242,7 +242,7 @@ elif _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] &&
|
|||
export GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
echo "AUTH_METHOD=curl"
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
export GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
export GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
echo "AUTH_METHOD=curl"
|
||||
else
|
||||
echo "AUTH_METHOD=none"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
|||
|
||||
```bash
|
||||
# Read from git credential store
|
||||
grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|'
|
||||
uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py"
|
||||
```
|
||||
|
||||
### 辅助函数:检测认证方式
|
||||
|
|
@ -242,7 +242,7 @@ elif [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
|
|||
export GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
echo "AUTH_METHOD=curl"
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
export GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
export GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
echo "AUTH_METHOD=curl"
|
||||
else
|
||||
echo "AUTH_METHOD=none"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ else
|
|||
if [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ else
|
|||
if [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ else
|
|||
if [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ else
|
|||
if [ -f ~/.hermes/.env ] && grep -q "^GITHUB_TOKEN=" ~/.hermes/.env; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" ~/.hermes/.env | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(grep "github.com" ~/.git-credentials 2>/dev/null | head -1 | sed 's|https://[^:]*:\([^@]*\)@.*|\1|')
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue