From ef6fbef5597c91b6b25742d04724c6fb93f46af5 Mon Sep 17 00:00:00 2001 From: lxowalle Date: Sun, 12 Apr 2026 20:30:51 +0800 Subject: [PATCH] fix github blob skill URL installs and metadata links --- pkg/skills/github_registry.go | 6 +++- pkg/skills/github_registry_test.go | 5 ++++ pkg/skills/installer.go | 13 +++++++-- pkg/skills/installer_test.go | 44 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/pkg/skills/github_registry.go b/pkg/skills/github_registry.go index feaf169d4..5597eda38 100644 --- a/pkg/skills/github_registry.go +++ b/pkg/skills/github_registry.go @@ -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) diff --git a/pkg/skills/github_registry_test.go b/pkg/skills/github_registry_test.go index db816845a..754291ba1 100644 --- a/pkg/skills/github_registry_test.go +++ b/pkg/skills/github_registry_test.go @@ -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) { diff --git a/pkg/skills/installer.go b/pkg/skills/installer.go index e51115faa..d4ca2ac15 100644 --- a/pkg/skills/installer.go +++ b/pkg/skills/installer.go @@ -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,7 +467,11 @@ 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 != "" { - urlPath = path.Join(urlPath, 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) diff --git a/pkg/skills/installer_test.go b/pkg/skills/installer_test.go index 0829317c4..83e241e5f 100644 --- a/pkg/skills/installer_test.go +++ b/pkg/skills/installer_test.go @@ -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