Commit 2caa9cd
Changed files (5)
cmd
del
internal
cmd/del/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"log"
"github.com/spf13/cobra"
@@ -8,31 +9,18 @@ import (
)
func main() {
- var provider, model string
-
+ var model string
rootCmd := &cobra.Command{
Use: "del",
- Short: "Del the Funky Robosapien - Your AI-powered coding superhero",
+ Short: "Del the Funky Robosapien - Claude Code replacement with local AI",
Run: func(cmd *cobra.Command, args []string) {
- var ai del.AIProvider
-
- switch provider {
- case "claude":
- ai = del.NewClaudeProvider("")
- case "ollama":
- ai = del.NewOllamaProvider(model)
- default:
- ai = del.AutoProvider("", model)
- }
-
+ ai := del.NewOllamaProvider(model)
assistant := del.NewDel(ai)
- assistant.StartREPL()
+ assistant.StartREPL(context.Background())
},
}
- rootCmd.Flags().StringVarP(&provider, "provider", "p", "auto", "AI provider (claude, ollama, auto)")
- rootCmd.Flags().StringVarP(&model, "model", "m", "deepseek-coder-v2:16b", "Model to use (for Ollama)")
-
+ rootCmd.Flags().StringVarP(&model, "model", "m", "deepseek-coder-v2:16b", "Ollama model to use")
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
internal/del/assistant.go
@@ -2,34 +2,29 @@ package del
import (
"bufio"
+ "context"
+ "encoding/json"
"fmt"
"os"
"strings"
)
-type Tool struct {
- Name string
- Description string
- Handler func(args map[string]interface{}) (interface{}, error)
-}
-
type Del struct {
aiProvider AIProvider
tools map[string]*Tool
- mcpServers map[string]string
}
-func NewDel(provider AIProvider) *Del {
- return &Del{
- aiProvider: provider,
- tools: make(map[string]*Tool),
- mcpServers: make(map[string]string),
+func NewDel(ai AIProvider) *Del {
+ d := &Del{
+ aiProvider: ai,
+ tools: map[string]*Tool{},
}
+ d.registerTools()
+ return d
}
-func (d *Del) StartREPL() {
- fmt.Printf("๐ค Del (%s) is ready!\n", d.aiProvider.Name())
- fmt.Println("Type 'quit' to exit.")
+func (d *Del) StartREPL(ctx context.Context) {
+ fmt.Println("๐ค Del is ready with", d.aiProvider.Name())
scanner := bufio.NewScanner(os.Stdin)
for {
@@ -37,11 +32,63 @@ func (d *Del) StartREPL() {
if !scanner.Scan() {
break
}
- input := strings.TrimSpace(scanner.Text())
- if input == "quit" || input == "exit" {
- fmt.Println("๐ค Del says: Peace out! โ๏ธ")
- break
+ line := scanner.Text()
+ if strings.HasPrefix(line, "/") {
+ d.handleSlash(line)
+ continue
+ }
+
+ prompt := d.buildPrompt(line)
+ response, err := d.aiProvider.Generate(ctx, prompt)
+ if err != nil {
+ fmt.Println("โ Error:", err)
+ continue
+ }
+
+ fmt.Println("๐ค Del:", response)
+ d.handleToolResponse(response)
+ }
+}
+
+func (d *Del) handleSlash(line string) {
+ fields := strings.Fields(line)
+ switch fields[0] {
+ case "/quit":
+ fmt.Println("๐ Bye!")
+ os.Exit(0)
+ case "/model":
+ fmt.Println("Current model:", d.aiProvider.Name())
+ default:
+ fmt.Println("โ Unknown command:", line)
+ }
+}
+
+func (d *Del) handleToolResponse(response string) {
+ for _, line := range strings.Split(response, " ") {
+ if strings.HasPrefix(line, "TOOL_USE:") {
+ parts := strings.SplitN(line[len("TOOL_USE:"):], " ", 2)
+ if len(parts) != 2 {
+ fmt.Println("โ Malformed tool call:", line)
+ continue
+ }
+ name := strings.TrimSpace(parts[0])
+ argsJSON := strings.TrimSpace(parts[1])
+ tool, ok := d.tools[name]
+ if !ok {
+ fmt.Println("โ Unknown tool:", name)
+ continue
+ }
+ var args map[string]interface{}
+ if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
+ fmt.Println("โ JSON error:", err)
+ continue
+ }
+ result, err := tool.Handler(args)
+ if err != nil {
+ fmt.Println("โ Tool error:", err)
+ } else {
+ fmt.Printf("โ
%s result: %+v ", name, result)
+ }
}
- fmt.Printf("๐ค (mock reply): You said '%s'\n", input)
}
}
internal/del/prompt.go
@@ -0,0 +1,44 @@
+package del
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+func (d *Del) buildPrompt(input string) string {
+ tools := []string{}
+ for name, tool := range d.tools {
+ tools = append(tools, fmt.Sprintf("- %s: %s", name, tool.Description))
+ }
+
+ return fmt.Sprintf(`You are Del the Funky Robosapien ๐ค๐ค
+
+Available tools:
+%s
+
+Working dir: %s
+Git status:
+%s
+
+User input:
+%s
+
+If you need a tool, respond like:
+TOOL_USE: read_file {"path": "main.go"}
+`, strings.Join(tools, " "), getCWD(), getGitStatus(), input)
+}
+
+func getCWD() string {
+ cwd, _ := os.Getwd()
+ return cwd
+}
+
+func getGitStatus() string {
+ out, err := exec.Command("git", "status", "--short").Output()
+ if err != nil {
+ return "Not a git repo"
+ }
+ return string(out)
+}
internal/del/provider.go
@@ -1,61 +1,33 @@
package del
import (
- "context"
- "fmt"
- "os/exec"
+ "context"
+ "fmt"
+ "os/exec"
)
type AIProvider interface {
- Generate(ctx context.Context, prompt string) (string, error)
- Name() string
-}
-
-type ClaudeProvider struct {
- apiKey string
-}
-
-func NewClaudeProvider(apiKey string) AIProvider {
- return &ClaudeProvider{apiKey: apiKey}
-}
-
-func (c *ClaudeProvider) Generate(ctx context.Context, prompt string) (string, error) {
- cmd := exec.CommandContext(ctx, "claude", "--print", prompt)
- output, err := cmd.Output()
- if err != nil {
- return "", fmt.Errorf("claude error: %w", err)
- }
- return string(output), nil
-}
-
-func (c *ClaudeProvider) Name() string {
- return "Claude"
+ Generate(ctx context.Context, prompt string) (string, error)
+ Name() string
}
type OllamaProvider struct {
- model string
+ model string
}
func NewOllamaProvider(model string) AIProvider {
- return &OllamaProvider{model: model}
+ return &OllamaProvider{model: model}
}
func (o *OllamaProvider) Generate(ctx context.Context, prompt string) (string, error) {
- cmd := exec.CommandContext(ctx, "ollama", "run", o.model, prompt)
- output, err := cmd.Output()
- if err != nil {
- return "", fmt.Errorf("ollama error: %w", err)
- }
- return string(output), nil
+ cmd := exec.CommandContext(ctx, "ollama", "run", o.model, prompt)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("ollama error: %w\n%s", err, string(output))
+ }
+ return string(output), nil
}
func (o *OllamaProvider) Name() string {
- return fmt.Sprintf("Ollama (%s)", o.model)
-}
-
-func AutoProvider(apiKey, model string) AIProvider {
- if exec.Command("claude", "--version").Run() == nil {
- return NewClaudeProvider(apiKey)
- }
- return NewOllamaProvider(model)
+ return fmt.Sprintf("Ollama (%s)", o.model)
}
internal/del/tools.go
@@ -0,0 +1,66 @@
+package del
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+type Tool struct {
+ Name string
+ Description string
+ Handler func(args map[string]interface{}) (interface{}, error)
+}
+
+func (d *Del) registerTools() {
+ d.tools["read_file"] = &Tool{"read_file", "Read a file from disk", d.readFile}
+ d.tools["write_file"] = &Tool{"write_file", "Write content to a file", d.writeFile}
+ d.tools["run_command"] = &Tool{"run_command", "Execute a shell command", d.runCommand}
+ d.tools["analyze_code"] = &Tool{"analyze_code", "Analyze code quality", d.analyzeCode}
+ d.tools["mcp_git_list"] = &Tool{"mcp_git_list", "MCP: List repo files", d.stubMCP}
+ d.tools["mcp_git_read"] = &Tool{"mcp_git_read", "MCP: Read repo file", d.stubMCP}
+}
+
+func (d *Del) readFile(args map[string]interface{}) (interface{}, error) {
+ path, ok := args["path"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'path'")
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ return string(data), nil
+}
+
+func (d *Del) writeFile(args map[string]interface{}) (interface{}, error) {
+ path, ok := args["path"].(string)
+ content, ok2 := args["content"].(string)
+ if !ok || !ok2 {
+ return nil, fmt.Errorf("missing 'path' or 'content'")
+ }
+ return "written", os.WriteFile(path, []byte(content), 0644)
+}
+
+func (d *Del) runCommand(args map[string]interface{}) (interface{}, error) {
+ command, ok := args["command"].(string)
+ if !ok {
+ return nil, fmt.Errorf("missing 'command'")
+ }
+ output, err := exec.Command("sh", "-c", command).CombinedOutput()
+ return string(output), err
+}
+
+func (d *Del) analyzeCode(args map[string]interface{}) (interface{}, error) {
+ return map[string]interface{}{
+ "quality": "good",
+ "suggestions": []string{
+ "Consider breaking large functions into smaller ones.",
+ "Add more comments for readability.",
+ },
+ }, nil
+}
+
+func (d *Del) stubMCP(args map[string]interface{}) (interface{}, error) {
+ return map[string]string{"status": "MCP stub - not connected"}, nil
+}