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:
parent
306e67f32d
commit
7d0e4510b8
2 changed files with 46 additions and 7 deletions
|
|
@ -455,6 +455,8 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
||||||
|
|
||||||
if bundle and "SKILL.md" in bundle.files:
|
if bundle and "SKILL.md" in bundle.files:
|
||||||
content = bundle.files["SKILL.md"]
|
content = bundle.files["SKILL.md"]
|
||||||
|
if isinstance(content, bytes):
|
||||||
|
content = content.decode("utf-8", errors="replace")
|
||||||
# Show first 50 lines as preview
|
# Show first 50 lines as preview
|
||||||
lines = content.split("\n")
|
lines = content.split("\n")
|
||||||
preview = "\n".join(lines[:50])
|
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("Repo", style="bold cyan")
|
||||||
table.add_column("Path", style="dim")
|
table.add_column("Path", style="dim")
|
||||||
for t in taps:
|
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(table)
|
||||||
c.print()
|
c.print()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ class GitHubSource(SkillSource):
|
||||||
|
|
||||||
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
||||||
try:
|
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:
|
if resp.status_code != 200:
|
||||||
return []
|
return []
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError:
|
||||||
|
|
@ -407,7 +407,7 @@ class GitHubSource(SkillSource):
|
||||||
"""Recursively download all text files from a GitHub directory."""
|
"""Recursively download all text files from a GitHub directory."""
|
||||||
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
url = f"https://api.github.com/repos/{repo}/contents/{path.rstrip('/')}"
|
||||||
try:
|
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:
|
if resp.status_code != 200:
|
||||||
return {}
|
return {}
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError:
|
||||||
|
|
@ -441,7 +441,7 @@ class GitHubSource(SkillSource):
|
||||||
resp = httpx.get(
|
resp = httpx.get(
|
||||||
url,
|
url,
|
||||||
headers={**self.auth.get_headers(), "Accept": "application/vnd.github.v3.raw"},
|
headers={**self.auth.get_headers(), "Accept": "application/vnd.github.v3.raw"},
|
||||||
timeout=15,
|
timeout=15, follow_redirects=True,
|
||||||
)
|
)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
return resp.text
|
return resp.text
|
||||||
|
|
@ -961,8 +961,8 @@ class SkillsShSource(SkillSource):
|
||||||
|
|
||||||
default_repo = f"{parts[0]}/{parts[1]}"
|
default_repo = f"{parts[0]}/{parts[1]}"
|
||||||
repo = detail.get("repo", default_repo) if isinstance(detail, dict) else default_repo
|
repo = detail.get("repo", default_repo) if isinstance(detail, dict) else default_repo
|
||||||
skill_token = parts[2]
|
skill_token=parts[2].split("/")[-1]
|
||||||
tokens = [skill_token]
|
tokens=[skill_token]
|
||||||
if isinstance(detail, dict):
|
if isinstance(detail, dict):
|
||||||
tokens.extend([
|
tokens.extend([
|
||||||
detail.get("install_skill", ""),
|
detail.get("install_skill", ""),
|
||||||
|
|
@ -970,7 +970,10 @@ class SkillsShSource(SkillSource):
|
||||||
detail.get("body_title", ""),
|
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:
|
try:
|
||||||
skills = self.github._list_skills_in_repo(repo, base_path)
|
skills = self.github._list_skills_in_repo(repo, base_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -978,6 +981,39 @@ class SkillsShSource(SkillSource):
|
||||||
for meta in skills:
|
for meta in skills:
|
||||||
if self._matches_skill_tokens(meta, tokens):
|
if self._matches_skill_tokens(meta, tokens):
|
||||||
return meta.identifier
|
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
|
return None
|
||||||
|
|
||||||
def _finalize_inspect_meta(self, meta: SkillMeta, canonical: str, detail: Optional[dict]) -> SkillMeta:
|
def _finalize_inspect_meta(self, meta: SkillMeta, canonical: str, detail: Optional[dict]) -> SkillMeta:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue