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 }