Merge PR #1524
This commit is contained in:
commit
dd82d8e32a
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
|
||||
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
|
||||
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]
|
||||
> **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 {
|
||||
fmt.Println("No skills installed.")
|
||||
printSkillSearchRoots(loader)
|
||||
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 {
|
||||
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:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
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
|
||||
entrypoint: ["picoclaw", "agent"]
|
||||
stdin_open: true
|
||||
|
|
@ -31,6 +34,9 @@ services:
|
|||
#extra_hosts:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
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
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
|
|
@ -49,4 +55,7 @@ services:
|
|||
- "127.0.0.1:18800:18800"
|
||||
- "127.0.0.1:18790:18790"
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue