diff --git a/agent/skill_utils.py b/agent/skill_utils.py index df0f933317fc..eea78d6a07c0 100644 --- a/agent/skill_utils.py +++ b/agent/skill_utils.py @@ -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 diff --git a/skills/github/github-auth/SKILL.md b/skills/github/github-auth/SKILL.md index 95606d36709b..b5e4057426a2 100644 --- a/skills/github/github-auth/SKILL.md +++ b/skills/github/github-auth/SKILL.md @@ -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" diff --git a/skills/github/github-auth/scripts/gh-env.sh b/skills/github/github-auth/scripts/gh-env.sh index 47b3ff98c5c8..5017aa732f5a 100755 --- a/skills/github/github-auth/scripts/gh-env.sh +++ b/skills/github/github-auth/scripts/gh-env.sh @@ -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 diff --git a/skills/github/github-auth/scripts/git-credential-token.py b/skills/github/github-auth/scripts/git-credential-token.py new file mode 100644 index 000000000000..7955aed6fac9 --- /dev/null +++ b/skills/github/github-auth/scripts/git-credential-token.py @@ -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()) diff --git a/skills/github/github-code-review/SKILL.md b/skills/github/github-code-review/SKILL.md index b58309235126..8f59405fb2f8 100644 --- a/skills/github/github-code-review/SKILL.md +++ b/skills/github/github-code-review/SKILL.md @@ -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 diff --git a/skills/github/github-issues/SKILL.md b/skills/github/github-issues/SKILL.md index bded118a10a9..415e10d449a5 100644 --- a/skills/github/github-issues/SKILL.md +++ b/skills/github/github-issues/SKILL.md @@ -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 diff --git a/skills/github/github-pr-workflow/SKILL.md b/skills/github/github-pr-workflow/SKILL.md index 69eb21183d3d..f076d08b623d 100644 --- a/skills/github/github-pr-workflow/SKILL.md +++ b/skills/github/github-pr-workflow/SKILL.md @@ -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 diff --git a/skills/github/github-repo-management/SKILL.md b/skills/github/github-repo-management/SKILL.md index 1026ce36e494..54565d7a10b5 100644 --- a/skills/github/github-repo-management/SKILL.md +++ b/skills/github/github-repo-management/SKILL.md @@ -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 diff --git a/tests/agent/test_skill_utils.py b/tests/agent/test_skill_utils.py index 471acd404195..4a860a2077da 100644 --- a/tests/agent/test_skill_utils.py +++ b/tests/agent/test_skill_utils.py @@ -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 ────────────────────────────────────── diff --git a/tests/skills/test_github_credential_token.py b/tests/skills/test_github_credential_token.py new file mode 100644 index 000000000000..433111fcf709 --- /dev/null +++ b/tests/skills/test_github_credential_token.py @@ -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 == [] diff --git a/tests/tools/test_skills_hub.py b/tests/tools/test_skills_hub.py index c6b13f85be2d..c8ae082dff95 100644 --- a/tests/tools/test_skills_hub.py +++ b/tests/tools/test_skills_hub.py @@ -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): diff --git a/tests/tools/test_skills_sync.py b/tests/tools/test_skills_sync.py index 47f957fda40c..062284a46049 100644 --- a/tests/tools/test_skills_sync.py +++ b/tests/tools/test_skills_sync.py @@ -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 == [] diff --git a/tools/skills_hub.py b/tools/skills_hub.py index ab186b403d0f..7753b56c89df 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -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 diff --git a/tools/skills_sync.py b/tools/skills_sync.py index de0545e308cb..288aa2dac51e 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -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: diff --git a/website/docs/user-guide/skills/bundled/github/github-github-auth.md b/website/docs/user-guide/skills/bundled/github/github-github-auth.md index 35e631fb2376..ba58277c4e96 100644 --- a/website/docs/user-guide/skills/bundled/github/github-github-auth.md +++ b/website/docs/user-guide/skills/bundled/github/github-github-auth.md @@ -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" diff --git a/website/docs/user-guide/skills/bundled/github/github-github-code-review.md b/website/docs/user-guide/skills/bundled/github/github-github-code-review.md index a7adc59e1197..504188699998 100644 --- a/website/docs/user-guide/skills/bundled/github/github-github-code-review.md +++ b/website/docs/user-guide/skills/bundled/github/github-github-code-review.md @@ -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 diff --git a/website/docs/user-guide/skills/bundled/github/github-github-issues.md b/website/docs/user-guide/skills/bundled/github/github-github-issues.md index fa3dc52c7e21..e25ceb4cb24e 100644 --- a/website/docs/user-guide/skills/bundled/github/github-github-issues.md +++ b/website/docs/user-guide/skills/bundled/github/github-github-issues.md @@ -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 diff --git a/website/docs/user-guide/skills/bundled/github/github-github-pr-workflow.md b/website/docs/user-guide/skills/bundled/github/github-github-pr-workflow.md index a0221be3d735..0536a0dd6a57 100644 --- a/website/docs/user-guide/skills/bundled/github/github-github-pr-workflow.md +++ b/website/docs/user-guide/skills/bundled/github/github-github-pr-workflow.md @@ -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 diff --git a/website/docs/user-guide/skills/bundled/github/github-github-repo-management.md b/website/docs/user-guide/skills/bundled/github/github-github-repo-management.md index b87a7abdf375..f540d02eb264 100644 --- a/website/docs/user-guide/skills/bundled/github/github-github-repo-management.md +++ b/website/docs/user-guide/skills/bundled/github/github-github-repo-management.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-auth.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-auth.md index 623fd03b9bed..ca85ae3f3bf9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-auth.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-auth.md @@ -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" diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-code-review.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-code-review.md index d9c20243da5b..479fc78270aa 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-code-review.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-code-review.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-issues.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-issues.md index 6b601aaf39df..cd7507b28450 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-issues.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-issues.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-pr-workflow.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-pr-workflow.md index b914f0ac4d38..9a1bb42e1570 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-pr-workflow.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-pr-workflow.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-repo-management.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-repo-management.md index 62d2b9ad775d..06f4f3289974 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-repo-management.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/github/github-github-repo-management.md @@ -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