From 64427f20820042e2ee654b64ad83b384480e9cfc Mon Sep 17 00:00:00 2001 From: Delqhi Date: Sun, 14 Jun 2026 11:31:36 +0200 Subject: [PATCH] fix(registry): fall back to default SIN_SKILLS_DIR when env var is unset The registry used os.Getenv(SIN_SKILLS_DIR) directly, which is empty by default. skillmgr installs skills into ~/.local/share/sin-code/skills when SIN_SKILLS_DIR is unset, so the registry now uses the same default path. This makes the websearch Go-native skill discoverable out of the box after 'sin-code skill install websearch' without requiring SIN_SKILLS_DIR to be set. Adds a test that verifies the default skills dir is used when the env var is unset and the binary exists there. --- cmd/sin-code/internal/mcpclient/registry.go | 13 ++++++++- .../internal/mcpclient/registry_test.go | 27 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/cmd/sin-code/internal/mcpclient/registry.go b/cmd/sin-code/internal/mcpclient/registry.go index 7b2b706..4a0e9f7 100644 --- a/cmd/sin-code/internal/mcpclient/registry.go +++ b/cmd/sin-code/internal/mcpclient/registry.go @@ -14,7 +14,7 @@ import ( // tool-name prefixes ("websearch__search", "browser__navigate", ...), which // the permission matrix gates via the "mcp" policy class. func DefaultServers() []ServerConfig { - skillsDir := os.Getenv("SIN_SKILLS_DIR") + skillsDir := skillsDirOrDefault() py := func(repo string) ServerConfig { name := shortName(repo) cfg := ServerConfig{Name: name, Transport: "stdio"} @@ -85,3 +85,14 @@ func shortName(repo string) string { } return repo } + +// skillsDirOrDefault returns the configured SIN_SKILLS_DIR or the default +// local share location used by skillmgr. This keeps the registry in sync +// with where skillmgr actually installs skills. +func skillsDirOrDefault() string { + if d := os.Getenv("SIN_SKILLS_DIR"); d != "" { + return d + } + home, _ := os.UserHomeDir() + return filepath.Join(home, ".local", "share", "sin-code", "skills") +} diff --git a/cmd/sin-code/internal/mcpclient/registry_test.go b/cmd/sin-code/internal/mcpclient/registry_test.go index 07d26a7..dfa2043 100644 --- a/cmd/sin-code/internal/mcpclient/registry_test.go +++ b/cmd/sin-code/internal/mcpclient/registry_test.go @@ -35,6 +35,9 @@ func TestDefaultServersWebsearchUsesLocalBinaryWhenPresent(t *testing.T) { } func TestDefaultServersWebsearchFallsBackToPathBinary(t *testing.T) { + // Use a HOME that has no sin-code skills checkout so the default skills + // dir check fails and the registry falls back to the binary on PATH. + t.Setenv("HOME", t.TempDir()) t.Setenv("SIN_SKILLS_DIR", "") for _, s := range DefaultServers() { if s.Name != "websearch" { @@ -47,3 +50,27 @@ func TestDefaultServersWebsearchFallsBackToPathBinary(t *testing.T) { } t.Fatal("websearch server not found in DefaultServers") } + +func TestDefaultServersWebsearchUsesDefaultSkillsDir(t *testing.T) { + home := t.TempDir() + bin := filepath.Join(home, ".local", "share", "sin-code", "skills", "web_search_bundle", "sin-websearch") + if err := os.MkdirAll(filepath.Dir(bin), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(bin, []byte("#!/bin/sh\necho fake"), 0o755); err != nil { + t.Fatal(err) + } + + t.Setenv("SIN_SKILLS_DIR", "") + t.Setenv("HOME", home) + for _, s := range DefaultServers() { + if s.Name != "websearch" { + continue + } + if s.Command != bin { + t.Fatalf("websearch command should use default skills dir binary %q, got %q", bin, s.Command) + } + return + } + t.Fatal("websearch server not found in DefaultServers") +}