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:
Teknium 2026-07-12 20:35:53 -07:00
parent 4854961d74
commit 9b97dea1e6
24 changed files with 251 additions and 27 deletions

View file

@ -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

View file

@ -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"

View file

@ -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

View 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())

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ──────────────────────────────────────

View 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 == []

View file

@ -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):

View file

@ -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 == []

View file

@ -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

View file

@ -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:

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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