Initial commit: Cyrene Plugins SDK + Plugin Manager
Extracted from Cyrene main repo (backend/pkg/plugins + backend/plugin-manager). Contains SDK interfaces (Plugin/Tool/HostAPI), 13 built-in plugins, ToolRegistry with call log ring buffer, and Plugin Manager REST API service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+158
@@ -0,0 +1,158 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
||||
)
|
||||
|
||||
type FilePlugin struct {
|
||||
sdk.BasePlugin
|
||||
dataDir string
|
||||
}
|
||||
|
||||
func NewFilePlugin(dataDir string) *FilePlugin {
|
||||
if dataDir == "" {
|
||||
dataDir = "/tmp/cyrene_data"
|
||||
}
|
||||
return &FilePlugin{dataDir: dataDir}
|
||||
}
|
||||
|
||||
func (p *FilePlugin) Metadata() sdk.PluginMetadata {
|
||||
return sdk.PluginMetadata{
|
||||
Name: "file", DisplayName: "File Operations", Version: "1.0.0",
|
||||
Description: "Sandboxed file operations: read, write, list, delete within DATA_DIR",
|
||||
Category: "system", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilePlugin) Tools() []sdk.Tool { return []sdk.Tool{&FileTool{dataDir: p.dataDir}} }
|
||||
|
||||
type FileTool struct {
|
||||
sdk.BaseTool
|
||||
dataDir string
|
||||
}
|
||||
|
||||
func (t *FileTool) Definition() sdk.ToolDefinition {
|
||||
return sdk.ToolDefinition{
|
||||
ID: "file_ops", Name: "file_ops", DisplayName: "File Operations",
|
||||
Description: "File operations within a sandboxed data directory. Read, write, list, check existence, delete.",
|
||||
Category: "system", Complexity: sdk.ComplexitySimple,
|
||||
DangerLevel: "medium",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{"type": "string", "enum": []string{"read", "write", "list", "exists", "delete"}},
|
||||
"path": map[string]interface{}{"type": "string"},
|
||||
"content": map[string]interface{}{"type": "string"},
|
||||
},
|
||||
"required": []string{"action", "path"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *FileTool) Validate(args map[string]interface{}) error {
|
||||
for _, k := range []string{"action", "path"} {
|
||||
if _, ok := args[k]; !ok {
|
||||
return fmt.Errorf("missing required parameter: %s", k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *FileTool) safePath(p string) (string, error) {
|
||||
clean := filepath.Clean(p)
|
||||
abs, err := filepath.Abs(filepath.Join(t.dataDir, clean))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("path resolution failed: %w", err)
|
||||
}
|
||||
if !strings.HasPrefix(abs, filepath.Clean(t.dataDir)+string(os.PathSeparator)) && abs != filepath.Clean(t.dataDir) {
|
||||
return "", fmt.Errorf("path traversal denied: %s", p)
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
func (t *FileTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
||||
action, _ := args["action"].(string)
|
||||
pathStr, _ := args["path"].(string)
|
||||
|
||||
safePath, err := t.safePath(pathStr)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "read":
|
||||
info, err := os.Stat(safePath)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: "cannot read a directory"}, nil
|
||||
}
|
||||
if info.Size() > 100*1024 {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: "file too large (>100KB)"}, nil
|
||||
}
|
||||
data, err := os.ReadFile(safePath)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: string(data)}, nil
|
||||
|
||||
case "write":
|
||||
content, _ := args["content"].(string)
|
||||
dir := filepath.Dir(safePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
if err := os.WriteFile(safePath, []byte(content), 0644); err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: fmt.Sprintf("Written %d bytes to %s", len(content), pathStr)}, nil
|
||||
|
||||
case "list":
|
||||
entries, err := os.ReadDir(safePath)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
var out strings.Builder
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
if e.IsDir() {
|
||||
out.WriteString(fmt.Sprintf("[DIR] %s/\n", e.Name()))
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("[FILE] %s (%d bytes)\n", e.Name(), info.Size()))
|
||||
}
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: out.String()}, nil
|
||||
|
||||
case "exists":
|
||||
info, err := os.Stat(safePath)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: fmt.Sprintf("Path does not exist: %s", pathStr)}, nil
|
||||
}
|
||||
kind := "file"
|
||||
if info.IsDir() {
|
||||
kind = "directory"
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: fmt.Sprintf("Path exists (%s): %s", kind, pathStr)}, nil
|
||||
|
||||
case "delete":
|
||||
info, err := os.Stat(safePath)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: "cannot delete a directory"}, nil
|
||||
}
|
||||
if err := os.Remove(safePath); err != nil {
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: true, Output: fmt.Sprintf("Deleted: %s", pathStr)}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "file_ops", Success: false, Error: "unknown action: " + action}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user