diff --git a/pkg/config/config.go b/pkg/config/config.go index 532adfaf7..218585a79 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,6 +7,7 @@ import ( "math/rand" "os" "path/filepath" + "strconv" "strings" "sync/atomic" "time" @@ -978,6 +979,18 @@ type SkillRegistryConfig struct { Param map[string]any `json:"-" yaml:"-" env:"-"` } +const ( + envSkillsClawHubEnabled = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_ENABLED" + envSkillsClawHubBaseURL = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_BASE_URL" + envSkillsClawHubAuthToken = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_AUTH_TOKEN" + envSkillsClawHubSearchPath = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_SEARCH_PATH" + envSkillsClawHubSkillsPath = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_SKILLS_PATH" + envSkillsClawHubDownloadPath = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_DOWNLOAD_PATH" + envSkillsClawHubTimeout = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_TIMEOUT" + envSkillsClawHubMaxZipSize = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_MAX_ZIP_SIZE" + envSkillsClawHubMaxResponseSize = "PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_MAX_RESPONSE_SIZE" +) + func (c *SkillRegistryConfig) DecodeParam(target any) error { if c == nil { return nil @@ -1164,6 +1177,7 @@ func LoadConfig(path string) (*Config, error) { if err = env.Parse(cfg); err != nil { return nil, err } + applySkillsRegistryEnvCompat(cfg) // Expand multi-key configs into separate entries for key-level failover cfg.ModelList = expandMultiKeyModels(cfg.ModelList) @@ -1182,6 +1196,61 @@ func LoadConfig(path string) (*Config, error) { return cfg, nil } +func applySkillsRegistryEnvCompat(cfg *Config) { + if cfg == nil { + return + } + + registryCfg, ok := cfg.Tools.Skills.Registries.Get("clawhub") + if !ok { + registryCfg = SkillRegistryConfig{ + Name: "clawhub", + Param: map[string]any{}, + } + } + if registryCfg.Param == nil { + registryCfg.Param = map[string]any{} + } + + if raw, ok := os.LookupEnv(envSkillsClawHubEnabled); ok { + if value, err := strconv.ParseBool(strings.TrimSpace(raw)); err == nil { + registryCfg.Enabled = value + } + } + if value, ok := os.LookupEnv(envSkillsClawHubBaseURL); ok { + registryCfg.BaseURL = value + } + if value, ok := os.LookupEnv(envSkillsClawHubAuthToken); ok { + registryCfg.AuthToken = *NewSecureString(value) + } + if value, ok := os.LookupEnv(envSkillsClawHubSearchPath); ok { + registryCfg.Param["search_path"] = value + } + if value, ok := os.LookupEnv(envSkillsClawHubSkillsPath); ok { + registryCfg.Param["skills_path"] = value + } + if value, ok := os.LookupEnv(envSkillsClawHubDownloadPath); ok { + registryCfg.Param["download_path"] = value + } + if raw, ok := os.LookupEnv(envSkillsClawHubTimeout); ok { + if value, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil { + registryCfg.Param["timeout"] = value + } + } + if raw, ok := os.LookupEnv(envSkillsClawHubMaxZipSize); ok { + if value, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil { + registryCfg.Param["max_zip_size"] = value + } + } + if raw, ok := os.LookupEnv(envSkillsClawHubMaxResponseSize); ok { + if value, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil { + registryCfg.Param["max_response_size"] = value + } + } + + cfg.Tools.Skills.Registries.Set("clawhub", registryCfg) +} + func makeBackup(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { return nil diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 323f58a93..5d29591bb 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1523,6 +1523,50 @@ func TestResolveGatewayLogLevel_UsesEnvOverrideAndNormalizesInvalid(t *testing.T } } +func TestLoadConfig_AppliesLegacyClawHubRegistryEnvOverrides(t *testing.T) { + dir := t.TempDir() + cfgPath := filepath.Join(dir, "config.json") + data := `{"version":2,"tools":{"skills":{"registries":{"clawhub":{"enabled":true,"base_url":"https://clawhub.ai"}}}}}` + if err := os.WriteFile(cfgPath, []byte(data), 0o600); err != nil { + t.Fatalf("setup: %v", err) + } + + t.Setenv(envSkillsClawHubBaseURL, "https://clawhub.example.com") + t.Setenv(envSkillsClawHubAuthToken, "clawhub-token-from-env") + t.Setenv(envSkillsClawHubEnabled, "false") + t.Setenv(envSkillsClawHubSearchPath, "/custom/search") + t.Setenv(envSkillsClawHubDownloadPath, "/custom/download") + t.Setenv(envSkillsClawHubTimeout, "17") + + cfg, err := LoadConfig(cfgPath) + if err != nil { + t.Fatalf("LoadConfig: %v", err) + } + + clawhub, ok := cfg.Tools.Skills.Registries.Get("clawhub") + if !ok { + t.Fatal("clawhub registry missing") + } + if clawhub.BaseURL != "https://clawhub.example.com" { + t.Fatalf("BaseURL = %q, want %q", clawhub.BaseURL, "https://clawhub.example.com") + } + if clawhub.AuthToken.String() != "clawhub-token-from-env" { + t.Fatalf("AuthToken = %q, want %q", clawhub.AuthToken.String(), "clawhub-token-from-env") + } + if clawhub.Enabled { + t.Fatal("Enabled = true, want false") + } + if got := clawhub.Param["search_path"]; got != "/custom/search" { + t.Fatalf("search_path = %v, want %q", got, "/custom/search") + } + if got := clawhub.Param["download_path"]; got != "/custom/download" { + t.Fatalf("download_path = %v, want %q", got, "/custom/download") + } + if got := clawhub.Param["timeout"]; got != 17 { + t.Fatalf("timeout = %v, want %d", got, 17) + } +} + func TestModelConfig_ExtraBodyRoundTrip(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "config.json")