/ internal / tools / clipboard.go
clipboard.go
 1  package tools
 2  
 3  import (
 4  	"bytes"
 5  	"context"
 6  	"encoding/json"
 7  	"fmt"
 8  	"os/exec"
 9  
10  	"github.com/Kocoro-lab/ShanClaw/internal/agent"
11  )
12  
13  type ClipboardTool struct{}
14  
15  type clipboardArgs struct {
16  	Action  string `json:"action"`
17  	Content string `json:"content,omitempty"`
18  }
19  
20  func (t *ClipboardTool) Info() agent.ToolInfo {
21  	return agent.ToolInfo{
22  		Name:        "clipboard",
23  		Description: "Read or write the system clipboard (macOS only). Use action 'read' to get clipboard contents, 'write' to set them.",
24  		Parameters: map[string]any{
25  			"type": "object",
26  			"properties": map[string]any{
27  				"action":  map[string]any{"type": "string", "description": "Action: 'read' or 'write'"},
28  				"content": map[string]any{"type": "string", "description": "Content to write (required for write action)"},
29  			},
30  		},
31  		Required: []string{"action"},
32  	}
33  }
34  
35  func (t *ClipboardTool) Run(ctx context.Context, argsJSON string) (agent.ToolResult, error) {
36  	var args clipboardArgs
37  	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
38  		return agent.ToolResult{Content: fmt.Sprintf("invalid arguments: %v", err), IsError: true}, nil
39  	}
40  
41  	switch args.Action {
42  	case "read":
43  		cmd := exec.CommandContext(ctx, "pbpaste")
44  		output, err := cmd.CombinedOutput()
45  		if err != nil {
46  			return agent.ToolResult{Content: fmt.Sprintf("clipboard read error: %v", err), IsError: true}, nil
47  		}
48  		return agent.ToolResult{Content: string(output)}, nil
49  
50  	case "write":
51  		if args.Content == "" {
52  			return agent.ToolResult{Content: "content is required for write action", IsError: true}, nil
53  		}
54  		cmd := exec.CommandContext(ctx, "pbcopy")
55  		cmd.Stdin = bytes.NewReader([]byte(args.Content))
56  		if err := cmd.Run(); err != nil {
57  			return agent.ToolResult{Content: fmt.Sprintf("clipboard write error: %v", err), IsError: true}, nil
58  		}
59  		return agent.ToolResult{Content: fmt.Sprintf("wrote %d bytes to clipboard", len(args.Content))}, nil
60  
61  	default:
62  		return agent.ToolResult{Content: fmt.Sprintf("unknown action: %q (use 'read' or 'write')", args.Action), IsError: true}, nil
63  	}
64  }
65  
66  func (t *ClipboardTool) RequiresApproval() bool { return true }
67  
68  func (t *ClipboardTool) IsReadOnlyCall(argsJSON string) bool {
69  	var args struct {
70  		Action string `json:"action"`
71  	}
72  	if json.Unmarshal([]byte(argsJSON), &args) != nil {
73  		return false
74  	}
75  	return args.Action == "read"
76  }