fix: skills hub inspect/resolve — 4 bugs
Cherry-picked from PR #2122 by @AtlasMeridia. 1. do_inspect bytes crash: bundle.files returns bytes for official skills, .split() expected str. Added decode guard. 2. GitHub redirects: three httpx.get calls missing follow_redirects=True, causing silent 301 failures on renamed orgs. 3. Skill discovery fallback: scan repo root directories when standard paths (skills/, .agents/skills/, .claude/skills/) miss. 4. tap list KeyError: t['repo'] crashes for local taps. Use safe .get().
This commit is contained in:
@@ -455,6 +455,8 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
||||
|
||||
if bundle and "SKILL.md" in bundle.files:
|
||||
content = bundle.files["SKILL.md"]
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode("utf-8", errors="replace")
|
||||
# Show first 50 lines as preview
|
||||
lines = content.split("\n")
|
||||
preview = "\n".join(lines[:50])
|
||||
@@ -640,7 +642,8 @@ def do_tap(action: str, repo: str = "", console: Optional[Console] = None) -> No
|
||||
table.add_column("Repo", style="bold cyan")
|
||||
table.add_column("Path", style="dim")
|
||||
for t in taps:
|
||||
table.add_row(t["repo"], t.get("path", "skills/"))
|
||||
label = t.get("repo") or t.get("name") or t.get("path", "unknown")
|
||||
table.add_row(label, t.get("path", "skills/"))
|
||||
c.print(table)
|
||||
c.print()
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ class GitHubSource(SkillSource):
|
||||
|
||||
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
||||
try:
|
||||
resp = httpx.get(url, headers=self.auth.get_headers(), timeout=15)
|
||||
resp = httpx.get(url, headers=self.auth.get_headers(), timeout=15, follow_redirects=True)
|
||||
if resp.status_code != 200:
|
||||
return []
|
||||
except httpx.HTTPError:
|
||||
@@ -407,7 +407,7 @@ class GitHubSource(SkillSource):
|
||||
"""Recursively download all text files from a GitHub directory."""
|
||||
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
||||
try:
|
||||
resp = httpx.get(url, headers=self.auth.get_headers(), timeout=15)
|
||||
resp = httpx.get(url, headers=self.auth.get_headers(), timeout=15, follow_redirects=True)
|
||||
if resp.status_code != 200:
|
||||
return {}
|
||||
except httpx.HTTPError:
|
||||
@@ -441,7 +441,7 @@ class GitHubSource(SkillSource):
|
||||
resp = httpx.get(
|
||||
url,
|
||||
headers={**self.auth.get_headers(), "Accept": "application/vnd.github.v3.raw"},
|
||||
timeout=15,
|
||||
timeout=15, follow_redirects=True,
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.text
|
||||
@@ -961,8 +961,8 @@ class SkillsShSource(SkillSource):
|
||||
|
||||
default_repo = f"{parts[0]}/{parts[1]}"
|
||||
repo = detail.get("repo", default_repo) if isinstance(detail, dict) else default_repo
|
||||
skill_token = parts[2]
|
||||
tokens = [skill_token]
|
||||
skill_token=parts[2].split("/")[-1]
|
||||
tokens=[skill_token]
|
||||
if isinstance(detail, dict):
|
||||
tokens.extend([
|
||||
detail.get("install_skill", ""),
|
||||
@@ -970,7 +970,10 @@ class SkillsShSource(SkillSource):
|
||||
detail.get("body_title", ""),
|
||||
])
|
||||
|
||||
for base_path in ("skills/", ".agents/skills/", ".claude/skills/"):
|
||||
# Standard skill paths
|
||||
base_paths = ["skills/", ".agents/skills/", ".claude/skills/"]
|
||||
|
||||
for base_path in base_paths:
|
||||
try:
|
||||
skills = self.github._list_skills_in_repo(repo, base_path)
|
||||
except Exception:
|
||||
@@ -978,6 +981,39 @@ class SkillsShSource(SkillSource):
|
||||
for meta in skills:
|
||||
if self._matches_skill_tokens(meta, tokens):
|
||||
return meta.identifier
|
||||
|
||||
# Fallback: scan repo root for directories that might contain skills
|
||||
try:
|
||||
root_url = f"https://api.github.com/repos/{repo}/contents/"
|
||||
resp = httpx.get(root_url, headers=self.github.auth.get_headers(),
|
||||
timeout=15, follow_redirects=True)
|
||||
if resp.status_code == 200:
|
||||
entries = resp.json()
|
||||
if isinstance(entries, list):
|
||||
for entry in entries:
|
||||
if entry.get("type") != "dir":
|
||||
continue
|
||||
dir_name = entry["name"]
|
||||
if dir_name.startswith(".") or dir_name.startswith("_"):
|
||||
continue
|
||||
if dir_name in ("skills", ".agents", ".claude"):
|
||||
continue # already tried
|
||||
# Try direct: repo/dir/skill_token
|
||||
direct_id = f"{repo}/{dir_name}/{skill_token}"
|
||||
meta = self.github.inspect(direct_id)
|
||||
if meta:
|
||||
return meta.identifier
|
||||
# Try listing skills in this directory
|
||||
try:
|
||||
skills = self.github._list_skills_in_repo(repo, dir_name + "/")
|
||||
except Exception:
|
||||
continue
|
||||
for meta in skills:
|
||||
if self._matches_skill_tokens(meta, tokens):
|
||||
return meta.identifier
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _finalize_inspect_meta(self, meta: SkillMeta, canonical: str, detail: Optional[dict]) -> SkillMeta:
|
||||
|
||||
Reference in New Issue
Block a user