Commit db0e8a2

mo khan <mo@mokhan.ca>
2025-06-22 05:51:43
feat: add more tools
1 parent 3e02523
Changed files (1)
cmd
cmd/del/main.go
@@ -52,6 +52,13 @@ Help developers write better, safer, and more efficient code through intelligent
 - format_code: Auto-format code {"path": string}
 - lint_code: Run linters {"path": string}
 
+๐Ÿ”€ Git Operations:
+- git_operation: Git commands {"operation": string, "files": string, "message": string, "branch": string}
+
+๐ŸŒ Web & Testing:
+- web_search: Search for information {"query": string}
+- test_runner: Run project tests {"type": string, "path": string}
+
 # INTERACTION STYLE
 
 โœ… DO:
@@ -80,6 +87,18 @@ User: "how do I optimize this algorithm?"
 User: "scan this project for security issues"
 TOOL_USE: security_scan {"path": "."}
 
+User: "check git status"
+TOOL_USE: git_operation {"operation": "status"}
+
+User: "commit these changes with message fix bug"
+TOOL_USE: git_operation {"operation": "commit", "message": "fix bug"}
+
+User: "search for golang best practices"
+TOOL_USE: web_search {"query": "golang best practices"}
+
+User: "run the tests"
+TOOL_USE: test_runner {"type": "", "path": "."}
+
 Stay funky, keep coding! ๐ŸŽต`
 
 type Tool struct {
@@ -471,6 +490,301 @@ func isImportantFile(name string) bool {
 	return false
 }
 
+// git_operation: Execute git commands safely
+func (d *Del) gitOperation(ctx context.Context, args map[string]interface{}, ch chan string) (interface{}, error) {
+	operation, ok := args["operation"].(string)
+	if !ok {
+		return nil, fmt.Errorf("missing 'operation' string argument")
+	}
+	
+	// Parse additional args
+	files, _ := args["files"].(string)
+	message, _ := args["message"].(string)
+	branch, _ := args["branch"].(string)
+	remote, _ := args["remote"].(string)
+	
+	var cmd *exec.Cmd
+	
+	switch operation {
+	case "status":
+		cmd = exec.CommandContext(ctx, "git", "status", "--porcelain=v1")
+		
+	case "status-verbose", "status-v":
+		cmd = exec.CommandContext(ctx, "git", "status")
+		
+	case "add":
+		if files == "" {
+			files = "."
+		}
+		cmd = exec.CommandContext(ctx, "git", "add", files)
+		
+	case "commit":
+		if message == "" {
+			return nil, fmt.Errorf("commit operation requires 'message' argument")
+		}
+		cmd = exec.CommandContext(ctx, "git", "commit", "-m", message)
+		
+	case "push":
+		if remote == "" {
+			remote = "origin"
+		}
+		if branch == "" {
+			cmd = exec.CommandContext(ctx, "git", "push", remote)
+		} else {
+			cmd = exec.CommandContext(ctx, "git", "push", remote, branch)
+		}
+		
+	case "pull":
+		if remote == "" {
+			remote = "origin"
+		}
+		cmd = exec.CommandContext(ctx, "git", "pull", remote)
+		
+	case "diff":
+		if files == "" {
+			cmd = exec.CommandContext(ctx, "git", "diff")
+		} else {
+			cmd = exec.CommandContext(ctx, "git", "diff", files)
+		}
+		
+	case "diff-staged":
+		cmd = exec.CommandContext(ctx, "git", "diff", "--staged")
+		
+	case "log":
+		cmd = exec.CommandContext(ctx, "git", "log", "--oneline", "-10")
+		
+	case "log-verbose":
+		cmd = exec.CommandContext(ctx, "git", "log", "-5", "--stat")
+		
+	case "branch":
+		cmd = exec.CommandContext(ctx, "git", "branch", "-a")
+		
+	case "checkout":
+		if branch == "" {
+			return nil, fmt.Errorf("checkout operation requires 'branch' argument")
+		}
+		cmd = exec.CommandContext(ctx, "git", "checkout", branch)
+		
+	case "create-branch":
+		if branch == "" {
+			return nil, fmt.Errorf("create-branch operation requires 'branch' argument")
+		}
+		cmd = exec.CommandContext(ctx, "git", "checkout", "-b", branch)
+		
+	case "merge":
+		if branch == "" {
+			return nil, fmt.Errorf("merge operation requires 'branch' argument")
+		}
+		cmd = exec.CommandContext(ctx, "git", "merge", branch)
+		
+	case "stash":
+		cmd = exec.CommandContext(ctx, "git", "stash")
+		
+	case "stash-pop":
+		cmd = exec.CommandContext(ctx, "git", "stash", "pop")
+		
+	case "stash-list":
+		cmd = exec.CommandContext(ctx, "git", "stash", "list")
+		
+	case "remote":
+		cmd = exec.CommandContext(ctx, "git", "remote", "-v")
+		
+	case "reset":
+		if files == "" {
+			cmd = exec.CommandContext(ctx, "git", "reset", "--soft", "HEAD~1")
+		} else {
+			cmd = exec.CommandContext(ctx, "git", "reset", "HEAD", files)
+		}
+		
+	case "clean":
+		cmd = exec.CommandContext(ctx, "git", "clean", "-fd")
+		
+	default:
+		return nil, fmt.Errorf("unsupported git operation: %s", operation)
+	}
+	
+	output, err := cmd.CombinedOutput()
+	result := string(output)
+	
+	if err != nil {
+		result = fmt.Sprintf("Git command failed: %s\nOutput: %s", err, result)
+	}
+	
+	ch <- result
+	return result, nil
+}
+
+// web_search: Search the web for information  
+func (d *Del) webSearch(ctx context.Context, args map[string]interface{}, ch chan string) (interface{}, error) {
+	query, ok := args["query"].(string)
+	if !ok {
+		return nil, fmt.Errorf("missing 'query' string argument")
+	}
+	
+	// Use curl to search DuckDuckGo (simple text API)
+	searchURL := fmt.Sprintf("https://api.duckduckgo.com/?q=%s&format=json&no_html=1&skip_disambig=1", 
+		strings.ReplaceAll(query, " ", "+"))
+	
+	cmd := exec.CommandContext(ctx, "curl", "-s", searchURL)
+	output, err := cmd.Output()
+	if err != nil {
+		return nil, fmt.Errorf("web search failed: %w", err)
+	}
+	
+	// Parse the JSON response for abstract/answer
+	var response map[string]interface{}
+	if err := json.Unmarshal(output, &response); err != nil {
+		return string(output), nil // Return raw if parsing fails
+	}
+	
+	var result strings.Builder
+	result.WriteString(fmt.Sprintf("๐Ÿ” Search: %s\n\n", query))
+	
+	if abstract, ok := response["Abstract"].(string); ok && abstract != "" {
+		result.WriteString(fmt.Sprintf("๐Ÿ“– Summary: %s\n\n", abstract))
+	}
+	
+	if answer, ok := response["Answer"].(string); ok && answer != "" {
+		result.WriteString(fmt.Sprintf("๐Ÿ’ก Answer: %s\n\n", answer))
+	}
+	
+	if relatedTopics, ok := response["RelatedTopics"].([]interface{}); ok && len(relatedTopics) > 0 {
+		result.WriteString("๐Ÿ”— Related:\n")
+		for i, topic := range relatedTopics {
+			if i >= 3 {
+				break // Limit to 3 results
+			}
+			if topicMap, ok := topic.(map[string]interface{}); ok {
+				if text, ok := topicMap["Text"].(string); ok {
+					result.WriteString(fmt.Sprintf("  โ€ข %s\n", text))
+				}
+			}
+		}
+	}
+	
+	finalResult := result.String()
+	ch <- finalResult
+	return finalResult, nil
+}
+
+// test_runner: Detect and run tests in the project
+func (d *Del) testRunner(ctx context.Context, args map[string]interface{}, ch chan string) (interface{}, error) {
+	testType, _ := args["type"].(string)
+	path, _ := args["path"].(string)
+	if path == "" {
+		path = "."
+	}
+	
+	var cmd *exec.Cmd
+	var output strings.Builder
+	
+	// Auto-detect test framework if not specified
+	if testType == "" {
+		output.WriteString("๐Ÿ” Detecting test framework...\n")
+		
+		// Check for Go tests
+		if _, err := os.Stat("go.mod"); err == nil {
+			testType = "go"
+			output.WriteString("โœ… Found Go project\n")
+		}
+		// Check for Node.js tests
+		if _, err := os.Stat("package.json"); err == nil {
+			if testType != "" {
+				testType = "multi"
+			} else {
+				testType = "node"
+				output.WriteString("โœ… Found Node.js project\n")
+			}
+		}
+		// Check for Python tests
+		if _, err := os.Stat("requirements.txt"); err == nil || 
+		   _, err := os.Stat("pyproject.toml"); err == nil {
+			if testType != "" {
+				testType = "multi"
+			} else {
+				testType = "python"
+				output.WriteString("โœ… Found Python project\n")
+			}
+		}
+		// Check for Rust tests
+		if _, err := os.Stat("Cargo.toml"); err == nil {
+			if testType != "" {
+				testType = "multi"
+			} else {
+				testType = "rust"
+				output.WriteString("โœ… Found Rust project\n")
+			}
+		}
+	}
+	
+	output.WriteString(fmt.Sprintf("๐Ÿš€ Running %s tests...\n\n", testType))
+	
+	switch testType {
+	case "go":
+		cmd = exec.CommandContext(ctx, "go", "test", "./...")
+		
+	case "node", "npm":
+		cmd = exec.CommandContext(ctx, "npm", "test")
+		
+	case "yarn":
+		cmd = exec.CommandContext(ctx, "yarn", "test")
+		
+	case "python", "pytest":
+		cmd = exec.CommandContext(ctx, "python", "-m", "pytest", path)
+		
+	case "python-unittest":
+		cmd = exec.CommandContext(ctx, "python", "-m", "unittest", "discover", path)
+		
+	case "rust", "cargo":
+		cmd = exec.CommandContext(ctx, "cargo", "test")
+		
+	case "make":
+		cmd = exec.CommandContext(ctx, "make", "test")
+		
+	case "multi":
+		// For multi-language projects, try the most common
+		output.WriteString("๐Ÿ“ Multi-language project detected. Trying common test commands:\n\n")
+		
+		// Try Go first
+		if _, err := os.Stat("go.mod"); err == nil {
+			output.WriteString("๐Ÿน Running Go tests:\n")
+			goCmd := exec.CommandContext(ctx, "go", "test", "./...")
+			if goOut, err := goCmd.CombinedOutput(); err == nil {
+				output.WriteString(string(goOut))
+				output.WriteString("\n")
+			}
+		}
+		
+		// Try npm if package.json exists
+		if _, err := os.Stat("package.json"); err == nil {
+			output.WriteString("๐Ÿ“ฆ Running npm tests:\n")
+			npmCmd := exec.CommandContext(ctx, "npm", "test")
+			if npmOut, err := npmCmd.CombinedOutput(); err == nil {
+				output.WriteString(string(npmOut))
+			}
+		}
+		
+		result := output.String()
+		ch <- result
+		return result, nil
+		
+	default:
+		return nil, fmt.Errorf("unsupported test type: %s. Supported: go, node, python, rust, make", testType)
+	}
+	
+	testOutput, err := cmd.CombinedOutput()
+	result := output.String() + string(testOutput)
+	
+	if err != nil {
+		result += fmt.Sprintf("\nโŒ Tests failed with error: %s", err)
+	} else {
+		result += "\nโœ… Tests completed successfully!"
+	}
+	
+	ch <- result
+	return result, nil
+}
+
 // mcp_call (stub for now)
 func (d *Del) stubMCP(ctx context.Context, args map[string]interface{}, ch chan string) (interface{}, error) {
 	endpoint, _ := args["endpoint"].(string)
@@ -573,6 +887,21 @@ Always assume "." (current directory) when no path is specified for file operati
 		Description: "Analyze current project structure and provide development context",
 		Handler:     d.projectContext,
 	}
+	d.tools["git_operation"] = &Tool{
+		Name:        "git_operation",
+		Description: "Execute git operations (status, add, commit, push, pull, diff, log, branch, etc)",
+		Handler:     d.gitOperation,
+	}
+	d.tools["web_search"] = &Tool{
+		Name:        "web_search",
+		Description: "Search the web for information and documentation",
+		Handler:     d.webSearch,
+	}
+	d.tools["test_runner"] = &Tool{
+		Name:        "test_runner",
+		Description: "Detect and run tests for various languages (Go, Node.js, Python, Rust)",
+		Handler:     d.testRunner,
+	}
 	d.tools["mcp_call"] = &Tool{
 		Name:        "mcp_call",
 		Description: "Stub for MCP integration",
@@ -647,16 +976,18 @@ func (d *Del) showHelp() {
   โ€ข "scan this project for vulnerabilities"
   โ€ข "read and explain main.go"
   โ€ข "what functions are in utils.py?"
-  โ€ข "format all the go files"
+  โ€ข "check git status and commit changes"
   โ€ข "run the tests"
-  โ€ข "show me the git status"
+  โ€ข "search for golang best practices"
   โ€ข "analyze the code quality"
 
 ๐Ÿ”ง Available Tools:
   โœ“ File Operations     โ€ข read, write, list files
   โœ“ Code Analysis       โ€ข analyze, format, lint code
   โœ“ Security Scanning   โ€ข find vulnerabilities
-  โœ“ Search & Navigate   โ€ข find functions, references
+  โœ“ Git Operations      โ€ข status, commit, push, diff, log
+  โœ“ Web Search          โ€ข search for documentation and info
+  โœ“ Test Running        โ€ข auto-detect and run project tests
   โœ“ Command Execution   โ€ข run any shell command
   โœ“ Project Understanding โ€ข context-aware assistance