fix github blob skill URL installs and metadata links

This commit is contained in:
lxowalle 2026-04-12 20:30:51 +08:00
parent 954ea3be14
commit ef6fbef559
4 changed files with 65 additions and 3 deletions

View file

@ -93,7 +93,11 @@ func (r *GitHubRegistry) SkillURL(target, version string) string {
return fmt.Sprintf("%s/%s", base, urlPath)
}
if ref.SubPath != "" {
return fmt.Sprintf("%s/%s/tree/%s/%s", base, urlPath, ref.Ref, ref.SubPath)
viewKind := "tree"
if isSkillMarkdownPath(ref.SubPath) {
viewKind = "blob"
}
return fmt.Sprintf("%s/%s/%s/%s/%s", base, urlPath, viewKind, ref.Ref, ref.SubPath)
}
if ref.Ref != "main" {
return fmt.Sprintf("%s/%s/tree/%s", base, urlPath, ref.Ref)

View file

@ -176,6 +176,11 @@ func TestGitHubRegistrySkillURLUsesProvidedVersionAndBasePath(t *testing.T) {
"https://ghe.example.com/git/org/repo/tree/feature/skills-registry/skills/pr-review",
registry.SkillURL("org/repo/skills/pr-review", "feature/skills-registry"),
)
assert.Equal(
t,
"https://ghe.example.com/git/org/repo/blob/main/.agents/skills/pr-review/SKILL.md",
registry.SkillURL("https://ghe.example.com/git/org/repo/blob/main/.agents/skills/pr-review/SKILL.md", ""),
)
}
func TestGitHubRegistryResolveInstallDirNameSupportsFullURLs(t *testing.T) {

View file

@ -217,7 +217,7 @@ func looksLikeSkillSubPath(subPath string) bool {
if subPath == "" {
return false
}
if subPath == "SKILL.md" || strings.HasSuffix(subPath, "/SKILL.md") {
if isSkillMarkdownPath(subPath) {
return true
}
parts := strings.Split(subPath, "/")
@ -230,6 +230,11 @@ func looksLikeSkillSubPath(subPath string) bool {
return false
}
func isSkillMarkdownPath(subPath string) bool {
subPath = strings.Trim(strings.TrimSpace(subPath), "/")
return subPath == "SKILL.md" || strings.HasSuffix(subPath, "/SKILL.md")
}
// parseGitHubRef parses a GitHub reference.
// Supports: "owner/repo", "owner/repo/path", or full URL like "https://github.com/owner/repo/tree/ref/path"
func parseGitHubRef(repo string) (GitHubRef, error) {
@ -462,8 +467,12 @@ func (si *SkillInstaller) getGithubDirAllFiles(ctx context.Context, apiURL, loca
func (si *SkillInstaller) downloadRaw(ctx context.Context, owner, repo, ref, subPath, localDir string) error {
urlPath := path.Join(owner, repo, ref)
if subPath != "" {
if isSkillMarkdownPath(subPath) {
urlPath = strings.TrimSuffix(path.Join(urlPath, subPath), "/SKILL.md")
} else {
urlPath = path.Join(urlPath, subPath)
}
}
url := fmt.Sprintf("%s/%s/SKILL.md", si.githubRawBaseURL, urlPath)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)

View file

@ -231,6 +231,50 @@ func TestSkillInstallerResolveGitHubRefUsesDefaultBranch(t *testing.T) {
}
}
func TestSkillInstallerInstallFromGitHubToDirSupportsBlobSkillURL(t *testing.T) {
tmpDir := t.TempDir()
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v3/repos/org/repo/contents/.agents/skills/pr-review/SKILL.md":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"type":"file","name":"SKILL.md"}`))
case "/raw/org/repo/main/.agents/skills/pr-review/SKILL.md":
_, _ = w.Write([]byte("---\nname: pr-review\ndescription: PR review skill\n---\n# PR Review\n"))
default:
t.Fatalf("unexpected path: %s", r.URL.Path)
}
}))
defer server.Close()
installer, err := NewSkillInstallerWithBaseURL(tmpDir, server.URL, "", "")
if err != nil {
t.Fatalf("NewSkillInstallerWithBaseURL() error = %v", err)
}
targetDir := filepath.Join(tmpDir, "skills", "pr-review")
result, err := installer.InstallFromGitHubToDir(
context.Background(),
server.URL+"/org/repo/blob/main/.agents/skills/pr-review/SKILL.md",
"",
targetDir,
)
if err != nil {
t.Fatalf("InstallFromGitHubToDir() error = %v", err)
}
if result.Version != "main" {
t.Fatalf("version = %q, want main", result.Version)
}
content, err := os.ReadFile(filepath.Join(targetDir, "SKILL.md"))
if err != nil {
t.Fatalf("ReadFile(SKILL.md) error = %v", err)
}
if !strings.Contains(string(content), "name: pr-review") {
t.Fatalf("SKILL.md content = %q, want skill metadata", string(content))
}
}
func TestShouldDownload(t *testing.T) {
tests := []struct {
name string