diff --git a/pkg/config/config_struct.go b/pkg/config/config_struct.go index a75677084..17738959a 100644 --- a/pkg/config/config_struct.go +++ b/pkg/config/config_struct.go @@ -328,9 +328,35 @@ func (v SecureModelList) MarshalYAML() (any, error) { } func (v *SkillsRegistriesConfig) UnmarshalJSON(data []byte) error { - var list []*SkillRegistryConfig + var list []json.RawMessage if err := json.Unmarshal(data, &list); err == nil { - *v = list + decodedList := make([]*SkillRegistryConfig, 0, len(list)) + for _, item := range list { + var nameOnly struct { + Name string `json:"name"` + } + if err := json.Unmarshal(item, &nameOnly); err != nil { + return err + } + registry := cloneRegistryConfig(findRegistryConfigByName(*v, nameOnly.Name)) + if registry == nil { + registry = &SkillRegistryConfig{Name: nameOnly.Name} + } + if err := json.Unmarshal(item, registry); err != nil { + return err + } + decodedList = append(decodedList, registry) + } + if len(*v) > 0 { + for _, registry := range decodedList { + if registry == nil { + continue + } + v.Set(registry.Name, *registry) + } + return nil + } + *v = decodedList return nil } @@ -345,16 +371,16 @@ func (v *SkillsRegistriesConfig) UnmarshalJSON(data []byte) error { keys = append(keys, name) } sort.Strings(keys) - list = make([]*SkillRegistryConfig, 0, len(keys)) + decodedList := make([]*SkillRegistryConfig, 0, len(keys)) for _, name := range keys { var registry SkillRegistryConfig if err := json.Unmarshal(legacy[name], ®istry); err != nil { return err } registry.Name = name - list = append(list, ®istry) + decodedList = append(decodedList, ®istry) } - *v = list + *v = decodedList return nil } diff --git a/pkg/config/config_struct_test.go b/pkg/config/config_struct_test.go index f02897d28..422c9978f 100644 --- a/pkg/config/config_struct_test.go +++ b/pkg/config/config_struct_test.go @@ -287,6 +287,29 @@ func TestSkillsRegistriesConfigUnmarshalJSONPreservesDefaultRegistries(t *testin assert.Empty(t, github.Param) } +func TestSkillsRegistriesConfigUnmarshalJSONListPreservesDefaultRegistries(t *testing.T) { + registries := DefaultConfig().Tools.Skills.Registries + + err := json.Unmarshal([]byte(`[ + { + "name": "clawhub", + "base_url": "https://clawhub.example.com" + } + ]`), ®istries) + assert.NoError(t, err) + + clawhub, ok := registries.Get("clawhub") + assert.True(t, ok) + assert.True(t, clawhub.Enabled) + assert.Equal(t, "https://clawhub.example.com", clawhub.BaseURL) + + github, ok := registries.Get("github") + assert.True(t, ok) + assert.True(t, github.Enabled) + assert.Equal(t, "https://github.com", github.BaseURL) + assert.Empty(t, github.Param) +} + func TestSkillsRegistriesConfigUnmarshalYAMLAppendsNewRegistryToExistingSlice(t *testing.T) { registries := DefaultConfig().Tools.Skills.Registries diff --git a/pkg/skills/installer.go b/pkg/skills/installer.go index c88cc046c..599668baf 100644 --- a/pkg/skills/installer.go +++ b/pkg/skills/installer.go @@ -261,6 +261,14 @@ func parseGitHubRefWithBaseURL(repo, githubBaseURL, defaultRef string) (GitHubRe if len(parts) < 2 { return GitHubRef{}, fmt.Errorf("invalid GitHub URL") } + if len(parts) > 2 { + if parts[2] != "tree" && parts[2] != "blob" { + return GitHubRef{}, fmt.Errorf("invalid GitHub repository URL path %q", u.Path) + } + if len(parts) < 4 { + return GitHubRef{}, fmt.Errorf("invalid GitHub %s URL path %q", parts[2], u.Path) + } + } ref := GitHubRef{ Owner: parts[0], RepoName: parts[1], diff --git a/pkg/skills/installer_test.go b/pkg/skills/installer_test.go index 9de991354..6c334d00f 100644 --- a/pkg/skills/installer_test.go +++ b/pkg/skills/installer_test.go @@ -234,6 +234,30 @@ func TestParseGitHubRefWithBaseURL(t *testing.T) { if !strings.Contains(err.Error(), `invalid GitHub URL host "ghe.example.com"`) { t.Fatalf("unexpected scheme mismatch error = %v", err) } + + _, err = parseGitHubRefWithBaseURL( + "https://github.com/org/repo/pull/2442", + "", + "main", + ) + if err == nil { + t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid repository URL path error") + } + if !strings.Contains(err.Error(), `invalid GitHub repository URL path "/org/repo/pull/2442"`) { + t.Fatalf("unexpected PR URL error = %v", err) + } + + _, err = parseGitHubRefWithBaseURL( + "https://github.com/org/repo/tree", + "", + "main", + ) + if err == nil { + t.Fatal("parseGitHubRefWithBaseURL() error = nil, want invalid tree URL path error") + } + if !strings.Contains(err.Error(), `invalid GitHub tree URL path "/org/repo/tree"`) { + t.Fatalf("unexpected short tree URL error = %v", err) + } } func TestSkillInstallerResolveGitHubRefUsesDefaultBranch(t *testing.T) {