fix cli skills install origin metadata

This commit is contained in:
lxowalle 2026-04-10 14:56:29 +08:00
parent eac37366fa
commit 34eeaf5899
2 changed files with 97 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package skills
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
@ -11,12 +12,23 @@ import (
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/fileutil"
"github.com/sipeed/picoclaw/pkg/skills"
"github.com/sipeed/picoclaw/pkg/utils"
)
const skillsSearchMaxResults = 20
type installedSkillOriginMeta struct {
Version int `json:"version"`
OriginKind string `json:"origin_kind,omitempty"`
Registry string `json:"registry,omitempty"`
Slug string `json:"slug,omitempty"`
RegistryURL string `json:"registry_url,omitempty"`
InstalledVersion string `json:"installed_version,omitempty"`
InstalledAt int64 `json:"installed_at"`
}
func skillsListCmd(loader *skills.SkillsLoader) {
allSkills := loader.ListSkills()
@ -92,6 +104,21 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, target string)
fmt.Printf("\u26a0\ufe0f Warning: skill '%s' is flagged as suspicious.\n", target)
}
normalizedSlug := skills.NormalizeInstallTargetForRegistry(cfg.Tools.Skills, registry.Name(), target)
installedAt := time.Now().UnixMilli()
if err := writeInstalledSkillOriginMeta(targetDir, installedSkillOriginMeta{
Version: 1,
OriginKind: "third_party",
Registry: registry.Name(),
Slug: normalizedSlug,
RegistryURL: registry.SkillURL(normalizedSlug, result.Version),
InstalledVersion: result.Version,
InstalledAt: installedAt,
}); err != nil {
_ = os.RemoveAll(targetDir)
return fmt.Errorf("✗ failed to persist skill metadata: %w", err)
}
fmt.Printf("\u2713 Skill '%s' v%s installed successfully!\n", dirName, result.Version)
if result.Summary != "" {
fmt.Printf(" %s\n", result.Summary)
@ -100,6 +127,14 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, target string)
return nil
}
func writeInstalledSkillOriginMeta(targetDir string, meta installedSkillOriginMeta) error {
data, err := json.MarshalIndent(meta, "", " ")
if err != nil {
return err
}
return fileutil.WriteFileAtomic(filepath.Join(targetDir, ".skill-origin.json"), data, 0o600)
}
func skillsRemoveFromWorkspace(workspace, skillName string) error {
name := strings.TrimSpace(skillName)
name = strings.Trim(name, "/")

View file

@ -0,0 +1,62 @@
package skills
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestSkillsInstallFromRegistryWritesOriginMetadata(t *testing.T) {
workspace := t.TempDir()
cfg := config.DefaultConfig()
cfg.Agents.Defaults.Workspace = workspace
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v3/repos/foo/bar":
require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"default_branch": "master"}))
case "/api/v3/repos/foo/bar/contents/.agents/skills/pr-review":
assert.Equal(t, "ref=master", r.URL.RawQuery)
require.NoError(t, json.NewEncoder(w).Encode([]map[string]any{{
"type": "file",
"name": "SKILL.md",
"download_url": server.URL + "/raw/foo/bar/master/.agents/skills/pr-review/SKILL.md",
}}))
case "/raw/foo/bar/master/.agents/skills/pr-review/SKILL.md":
_, _ = w.Write([]byte("---\nname: pr-review\ndescription: PR review skill\n---\n# PR Review\n"))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
githubRegistry, ok := cfg.Tools.Skills.Registries.Get("github")
require.True(t, ok)
githubRegistry.BaseURL = server.URL
cfg.Tools.Skills.Registries.Set("github", githubRegistry)
target := server.URL + "/foo/bar/tree/master/.agents/skills/pr-review"
require.NoError(t, skillsInstallFromRegistry(cfg, "github", target))
metaPath := filepath.Join(workspace, "skills", "pr-review", ".skill-origin.json")
data, err := os.ReadFile(metaPath)
require.NoError(t, err)
var meta installedSkillOriginMeta
require.NoError(t, json.Unmarshal(data, &meta))
assert.Equal(t, "third_party", meta.OriginKind)
assert.Equal(t, "github", meta.Registry)
assert.Equal(t, "foo/bar/.agents/skills/pr-review", meta.Slug)
assert.Equal(t, server.URL+"/foo/bar/tree/master/.agents/skills/pr-review", meta.RegistryURL)
assert.Equal(t, "master", meta.InstalledVersion)
assert.NotZero(t, meta.InstalledAt)
}