fix(skills): show scanned roots when no skills found
This commit is contained in:
parent
c68b4f3903
commit
6bf92a59f8
4 changed files with 102 additions and 0 deletions
|
|
@ -179,10 +179,16 @@ docker compose -f docker/docker-compose.yml --profile gateway up
|
||||||
# 3. Set your API keys
|
# 3. Set your API keys
|
||||||
vim docker/data/config.json # Set provider API keys, bot tokens, etc.
|
vim docker/data/config.json # Set provider API keys, bot tokens, etc.
|
||||||
|
|
||||||
|
# Optional: add custom skills for this compose setup
|
||||||
|
mkdir -p docker/data/workspace/skills/my-skill
|
||||||
|
# put your SKILL.md at docker/data/workspace/skills/my-skill/SKILL.md
|
||||||
|
|
||||||
# 4. Start
|
# 4. Start
|
||||||
docker compose -f docker/docker-compose.yml --profile gateway up -d
|
docker compose -f docker/docker-compose.yml --profile gateway up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In this compose setup, PicoClaw reads the workspace from `docker/data/workspace` on the host, so local skills must live under `docker/data/workspace/skills`. The repo checkout's `workspace/skills` directory is not mounted into the container.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **Docker Users**: By default, the Gateway listens on `127.0.0.1` which is not accessible from the host. If you need to access the health endpoints or expose ports, set `PICOCLAW_GATEWAY_HOST=0.0.0.0` in your environment or update `config.json`.
|
> **Docker Users**: By default, the Gateway listens on `127.0.0.1` which is not accessible from the host. If you need to access the health endpoints or expose ports, set `PICOCLAW_GATEWAY_HOST=0.0.0.0` in your environment or update `config.json`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ func skillsListCmd(loader *skills.SkillsLoader) {
|
||||||
|
|
||||||
if len(allSkills) == 0 {
|
if len(allSkills) == 0 {
|
||||||
fmt.Println("No skills installed.")
|
fmt.Println("No skills installed.")
|
||||||
|
printSkillSearchRoots(loader)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +36,19 @@ func skillsListCmd(loader *skills.SkillsLoader) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printSkillSearchRoots(loader *skills.SkillsLoader) {
|
||||||
|
roots := loader.SkillRoots()
|
||||||
|
if len(roots) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Scanned skill roots:")
|
||||||
|
for i, root := range roots {
|
||||||
|
fmt.Printf(" %d. %s\n", i+1, root)
|
||||||
|
}
|
||||||
|
fmt.Printf("Install local skills under %s/<skill-name>/SKILL.md\n", roots[0])
|
||||||
|
}
|
||||||
|
|
||||||
func skillsInstallCmd(installer *skills.SkillInstaller, repo string) error {
|
func skillsInstallCmd(installer *skills.SkillInstaller, repo string) error {
|
||||||
fmt.Printf("Installing skill from %s...\n", repo)
|
fmt.Printf("Installing skill from %s...\n", repo)
|
||||||
|
|
||||||
|
|
|
||||||
73
cmd/picoclaw/internal/skills/helpers_test.go
Normal file
73
cmd/picoclaw/internal/skills/helpers_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
package skills
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
skillspkg "github.com/sipeed/picoclaw/pkg/skills"
|
||||||
|
)
|
||||||
|
|
||||||
|
func captureStdout(t *testing.T, fn func()) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
require.NoError(t, err)
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
fn()
|
||||||
|
|
||||||
|
require.NoError(t, w.Close())
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkillsListCmd_EmptyOutputIncludesSearchRoots(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
workspace := filepath.Join(tmp, "workspace")
|
||||||
|
global := filepath.Join(tmp, "global")
|
||||||
|
builtin := filepath.Join(tmp, "builtin")
|
||||||
|
loader := skillspkg.NewSkillsLoader(workspace, global, builtin)
|
||||||
|
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
skillsListCmd(loader)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, output, "No skills installed.")
|
||||||
|
assert.Contains(t, output, "Scanned skill roots:")
|
||||||
|
assert.Contains(t, output, filepath.Join(workspace, "skills"))
|
||||||
|
assert.Contains(t, output, global)
|
||||||
|
assert.Contains(t, output, builtin)
|
||||||
|
assert.Contains(t, output, filepath.Join(workspace, "skills", "<skill-name>", "SKILL.md"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkillsListCmd_ShowsInstalledSkills(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
workspace := filepath.Join(tmp, "workspace")
|
||||||
|
skillDir := filepath.Join(workspace, "skills", "weather")
|
||||||
|
require.NoError(t, os.MkdirAll(skillDir, 0o755))
|
||||||
|
|
||||||
|
content := "---\nname: weather\ndescription: Weather lookup\n---\n\n# Weather\n"
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte(content), 0o644))
|
||||||
|
|
||||||
|
loader := skillspkg.NewSkillsLoader(workspace, filepath.Join(tmp, "global"), filepath.Join(tmp, "builtin"))
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
skillsListCmd(loader)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, output, "Installed Skills:")
|
||||||
|
assert.Contains(t, output, "weather (workspace)")
|
||||||
|
assert.Contains(t, output, "Weather lookup")
|
||||||
|
assert.NotContains(t, output, "Scanned skill roots:")
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,9 @@ services:
|
||||||
#extra_hosts:
|
#extra_hosts:
|
||||||
# - "host.docker.internal:host-gateway"
|
# - "host.docker.internal:host-gateway"
|
||||||
volumes:
|
volumes:
|
||||||
|
# Persistent PicoClaw home. Custom local skills should be placed under
|
||||||
|
# ./data/workspace/skills on the host so they appear at
|
||||||
|
# /root/.picoclaw/workspace/skills inside the container.
|
||||||
- ./data:/root/.picoclaw
|
- ./data:/root/.picoclaw
|
||||||
entrypoint: ["picoclaw", "agent"]
|
entrypoint: ["picoclaw", "agent"]
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
|
|
@ -31,6 +34,9 @@ services:
|
||||||
#extra_hosts:
|
#extra_hosts:
|
||||||
# - "host.docker.internal:host-gateway"
|
# - "host.docker.internal:host-gateway"
|
||||||
volumes:
|
volumes:
|
||||||
|
# Persistent PicoClaw home. Custom local skills should be placed under
|
||||||
|
# ./data/workspace/skills on the host so they appear at
|
||||||
|
# /root/.picoclaw/workspace/skills inside the container.
|
||||||
- ./data:/root/.picoclaw
|
- ./data:/root/.picoclaw
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
|
|
@ -49,4 +55,7 @@ services:
|
||||||
- "127.0.0.1:18800:18800"
|
- "127.0.0.1:18800:18800"
|
||||||
- "127.0.0.1:18790:18790"
|
- "127.0.0.1:18790:18790"
|
||||||
volumes:
|
volumes:
|
||||||
|
# Persistent PicoClaw home. Custom local skills should be placed under
|
||||||
|
# ./data/workspace/skills on the host so they appear at
|
||||||
|
# /root/.picoclaw/workspace/skills inside the container.
|
||||||
- ./data:/root/.picoclaw
|
- ./data:/root/.picoclaw
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue