summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools.go591
-rw-r--r--tools/chain.go12
-rw-r--r--tools/fs.go74
3 files changed, 111 insertions, 566 deletions
diff --git a/tools.go b/tools.go
index 7f6ceb4..fa9c8b5 100644
--- a/tools.go
+++ b/tools.go
@@ -537,6 +537,7 @@ func getHelp(args []string) string {
mkdir <dir> - create directory
pwd - print working directory
cd <dir> - change directory
+ sed 's/old/new/[g]' [file] - text replacement
# Text processing
echo <args> - echo back input
@@ -551,6 +552,9 @@ func getHelp(args []string) string {
# Git (read-only)
git <cmd> - git commands (status, log, diff, show, branch, etc.)
+ # Go
+ go <cmd> - go commands (run, build, test, mod, etc.)
+
# Memory
memory store <topic> <data> - save to memory
memory get <topic> - retrieve from memory
@@ -650,6 +654,27 @@ Use: run "command" to execute.`
Print working directory.
Example:
run "pwd"`
+ case "sed":
+ return `sed 's/old/new/[g]' [file]
+ Stream editor for text replacement.
+ Options:
+ -i in-place editing
+ -g global replacement (replace all)
+ Examples:
+ run "sed 's/foo/bar/' file.txt"
+ run "sed 's/foo/bar/g' file.txt" (global)
+ run "sed -i 's/foo/bar/' file.txt" (in-place)
+ run "cat file.txt | sed 's/foo/bar/'" (pipe from stdin)`
+ case "go":
+ return `go <command>
+ Go toolchain commands.
+ Allowed: run, build, test, mod, get, install, clean, fmt, vet, etc.
+ Examples:
+ run "go run main.go"
+ run "go build ./..."
+ run "go test ./..."
+ run "go mod tidy"
+ run "go get github.com/package"`
default:
return fmt.Sprintf("No help available for: %s. Use: run \"help\" for all commands.", cmd)
}
@@ -931,65 +956,6 @@ func todoDelete(args map[string]string) []byte {
return jsonResult
}
-var gitReadSubcommands = map[string]bool{
- "status": true,
- "log": true,
- "diff": true,
- "show": true,
- "branch": true,
- "reflog": true,
- "rev-parse": true,
- "shortlog": true,
- "describe": true,
-}
-
-func isCommandAllowed(command string, args ...string) bool {
- allowedCommands := map[string]bool{
- "cd": true,
- "grep": true,
- "sed": true,
- "awk": true,
- "find": true,
- "cat": true,
- "head": true,
- "tail": true,
- "sort": true,
- "uniq": true,
- "wc": true,
- "ls": true,
- "echo": true,
- "cut": true,
- "tr": true,
- "cp": true,
- "mv": true,
- "rm": true,
- "mkdir": true,
- "rmdir": true,
- "pwd": true,
- "df": true,
- "free": true,
- "ps": true,
- "top": true,
- "du": true,
- "whoami": true,
- "date": true,
- "uname": true,
- "git": true,
- "go": true,
- }
- // Allow all go subcommands (go run, go mod tidy, go test, etc.)
- if strings.HasPrefix(command, "go ") && allowedCommands["go"] {
- return true
- }
- if command == "git" && len(args) > 0 {
- return gitReadSubcommands[args[0]]
- }
- if !allowedCommands[command] {
- return false
- }
- return true
-}
-
func summarizeChat(args map[string]string) []byte {
if len(chatBody.Messages) == 0 {
return []byte("No chat history to summarize.")
@@ -1150,108 +1116,6 @@ func argsToSlice(args map[string]string) []string {
return result
}
-func cmdLs(args map[string]string) []byte {
- return []byte(tools.FsLs(argsToSlice(args), ""))
-}
-
-func cmdCat(args map[string]string) []byte {
- return []byte(tools.FsCat(argsToSlice(args), ""))
-}
-
-func cmdSee(args map[string]string) []byte {
- return []byte(tools.FsSee(argsToSlice(args), ""))
-}
-
-func cmdWrite(args map[string]string) []byte {
- // write needs special handling - content might be in args or stdin
- slice := argsToSlice(args)
- // If there's a "content" key, append it
- if content, ok := args["content"]; ok && content != "" {
- slice = append(slice, content)
- }
- return []byte(tools.FsWrite(slice, ""))
-}
-
-func cmdStat(args map[string]string) []byte {
- return []byte(tools.FsStat(argsToSlice(args), ""))
-}
-
-func cmdRm(args map[string]string) []byte {
- return []byte(tools.FsRm(argsToSlice(args), ""))
-}
-
-func cmdCp(args map[string]string) []byte {
- return []byte(tools.FsCp(argsToSlice(args), ""))
-}
-
-func cmdMv(args map[string]string) []byte {
- return []byte(tools.FsMv(argsToSlice(args), ""))
-}
-
-func cmdMkdir(args map[string]string) []byte {
- return []byte(tools.FsMkdir(argsToSlice(args), ""))
-}
-
-func cmdEcho(args map[string]string) []byte {
- return []byte(tools.FsEcho(argsToSlice(args), ""))
-}
-
-func cmdTime(args map[string]string) []byte {
- return []byte(tools.FsTime(argsToSlice(args), ""))
-}
-
-func cmdGrep(args map[string]string) []byte {
- // grep needs special handling - pattern and flags
- slice := argsToSlice(args)
- // Check for pattern key
- if pattern, ok := args["pattern"]; ok && pattern != "" {
- slice = append([]string{pattern}, slice...)
- }
- return []byte(tools.FsGrep(slice, ""))
-}
-
-func cmdHead(args map[string]string) []byte {
- slice := argsToSlice(args)
- return []byte(tools.FsHead(slice, ""))
-}
-
-func cmdTail(args map[string]string) []byte {
- slice := argsToSlice(args)
- return []byte(tools.FsTail(slice, ""))
-}
-
-func cmdWc(args map[string]string) []byte {
- slice := argsToSlice(args)
- return []byte(tools.FsWc(slice, ""))
-}
-
-func cmdSort(args map[string]string) []byte {
- slice := argsToSlice(args)
- return []byte(tools.FsSort(slice, ""))
-}
-
-func cmdUniq(args map[string]string) []byte {
- slice := argsToSlice(args)
- return []byte(tools.FsUniq(slice, ""))
-}
-
-func cmdGit(args map[string]string) []byte {
- slice := argsToSlice(args)
- // Check for subcommand key
- if subcmd, ok := args["subcommand"]; ok && subcmd != "" {
- slice = append([]string{subcmd}, slice...)
- }
- return []byte(tools.FsGit(slice, ""))
-}
-
-func cmdPwd(args map[string]string) []byte {
- return []byte(tools.FsPwd(argsToSlice(args), ""))
-}
-
-func cmdCd(args map[string]string) []byte {
- return []byte(tools.FsCd(argsToSlice(args), ""))
-}
-
func cmdMemory(args map[string]string) []byte {
return []byte(tools.FsMemory(argsToSlice(args), ""))
}
@@ -1295,18 +1159,6 @@ var fnMap = map[string]fnSig{
"websearch_raw": websearchRaw,
"read_url": readURL,
"read_url_raw": readURLRaw,
- // Unix-style file commands (replacing file_* tools)
- "ls": cmdLs,
- "cat": cmdCat,
- "see": cmdSee,
- "write": cmdWrite,
- "stat": cmdStat,
- "rm": cmdRm,
- "cp": cmdCp,
- "mv": cmdMv,
- "mkdir": cmdMkdir,
- "pwd": cmdPwd,
- "cd": cmdCd,
// Unified run command
"run": runCmd,
"summarize_chat": summarizeChat,
@@ -1912,238 +1764,6 @@ var baseTools = []models.Tool{
models.Tool{
Type: "function",
Function: models.ToolFunc{
- Name: "memory",
- Description: "Memory management. Usage: memory store <topic> <data> | memory get <topic> | memory list | memory forget <topic>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"subcommand"},
- Properties: map[string]models.ToolArgProps{
- "subcommand": models.ToolArgProps{
- Type: "string",
- Description: "subcommand: store, get, list, topics, forget, delete",
- },
- "topic": models.ToolArgProps{
- Type: "string",
- Description: "topic/key for memory",
- },
- "data": models.ToolArgProps{
- Type: "string",
- Description: "data to store",
- },
- },
- },
- },
- },
- // Unix-style file commands
- // ls
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "ls",
- Description: "List files in a directory. Usage: ls [dir]",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "directory to list (optional, defaults to current directory)",
- },
- },
- },
- },
- },
- // cat
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "cat",
- Description: "Read file content. Usage: cat <path>. Use -b flag for base64 output (for binary files).",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"path"},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "path of the file to read",
- },
- },
- },
- },
- },
- // see
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "see",
- Description: "View an image file and return it for multimodal LLM viewing. Supports png, jpg, jpeg, gif, webp, svg.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"path"},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "path of the image file to view",
- },
- },
- },
- },
- },
- // write
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "write",
- Description: "Write content to a file. Will overwrite any content present. Usage: write <path> [content]. Use -b flag for base64 input (for binary files).",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"path"},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "path of the file to write to",
- },
- "content": models.ToolArgProps{
- Type: "string",
- Description: "content to write to the file",
- },
- },
- },
- },
- },
- // stat
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "stat",
- Description: "Get file information (size, type, modified time). Usage: stat <path>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"path"},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "path of the file to get info for",
- },
- },
- },
- },
- },
- // rm
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "rm",
- Description: "Delete a file. Usage: rm <path>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"path"},
- Properties: map[string]models.ToolArgProps{
- "path": models.ToolArgProps{
- Type: "string",
- Description: "path of the file to delete",
- },
- },
- },
- },
- },
- // cp
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "cp",
- Description: "Copy a file. Usage: cp <src> <dst>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"src", "dst"},
- Properties: map[string]models.ToolArgProps{
- "src": models.ToolArgProps{
- Type: "string",
- Description: "source file path",
- },
- "dst": models.ToolArgProps{
- Type: "string",
- Description: "destination file path",
- },
- },
- },
- },
- },
- // mv
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "mv",
- Description: "Move/rename a file. Usage: mv <src> <dst>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"src", "dst"},
- Properties: map[string]models.ToolArgProps{
- "src": models.ToolArgProps{
- Type: "string",
- Description: "source file path",
- },
- "dst": models.ToolArgProps{
- Type: "string",
- Description: "destination file path",
- },
- },
- },
- },
- },
- // mkdir
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "mkdir",
- Description: "Create a directory. Usage: mkdir <dir>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"dir"},
- Properties: map[string]models.ToolArgProps{
- "dir": models.ToolArgProps{
- Type: "string",
- Description: "directory path to create",
- },
- },
- },
- },
- },
- // pwd
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "pwd",
- Description: "Print working directory. Returns the current directory path.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{},
- },
- },
- },
- // cd
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "cd",
- Description: "Change working directory. Usage: cd <dir>",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"dir"},
- Properties: map[string]models.ToolArgProps{
- "dir": models.ToolArgProps{
- Type: "string",
- Description: "directory to change to",
- },
- },
- },
- },
- },
- // run - unified command
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
Name: "run",
Description: "Execute commands: shell, git, memory, todo. Usage: run \"<command>\". Examples: run \"ls -la\", run \"git status\", run \"memory store foo bar\", run \"memory get foo\", run \"todo create task\", run \"help\", run \"help memory\"",
Parameters: models.ToolFuncParams{
@@ -2158,165 +1778,4 @@ var baseTools = []models.Tool{
},
},
},
- // echo
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "echo",
- Description: "Echo back the input. Usage: echo [args] or pipe stdin",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "args": models.ToolArgProps{
- Type: "string",
- Description: "arguments to echo",
- },
- },
- },
- },
- },
- // time
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "time",
- Description: "Return the current time.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{},
- },
- },
- },
- // grep
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "grep",
- Description: "Filter lines matching a pattern. Usage: grep [-i] [-v] [-c] <pattern>. -i: ignore case, -v: invert match, -c: count matches.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"pattern"},
- Properties: map[string]models.ToolArgProps{
- "pattern": models.ToolArgProps{
- Type: "string",
- Description: "pattern to search for",
- },
- },
- },
- },
- },
- // head
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "head",
- Description: "Show first N lines. Usage: head [n] or head -n <n>. Default: 10",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "n": models.ToolArgProps{
- Type: "string",
- Description: "number of lines (optional, default 10)",
- },
- },
- },
- },
- },
- // tail
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "tail",
- Description: "Show last N lines. Usage: tail [n] or tail -n <n>. Default: 10",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "n": models.ToolArgProps{
- Type: "string",
- Description: "number of lines (optional, default 10)",
- },
- },
- },
- },
- },
- // wc
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "wc",
- Description: "Count lines, words, chars. Usage: wc [-l] [-w] [-c]. -l: lines, -w: words, -c: chars.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "flag": models.ToolArgProps{
- Type: "string",
- Description: "optional flag: -l, -w, or -c",
- },
- },
- },
- },
- },
- // sort
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "sort",
- Description: "Sort lines. Usage: sort [-r] [-n]. -r: reverse, -n: numeric sort.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "reverse": models.ToolArgProps{
- Type: "string",
- Description: "use -r for reverse sort",
- },
- "numeric": models.ToolArgProps{
- Type: "string",
- Description: "use -n for numeric sort",
- },
- },
- },
- },
- },
- // uniq
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "uniq",
- Description: "Remove duplicate lines. Usage: uniq [-c] to show count.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{},
- Properties: map[string]models.ToolArgProps{
- "count": models.ToolArgProps{
- Type: "string",
- Description: "use -c to show count of occurrences",
- },
- },
- },
- },
- },
- // git (read-only)
- models.Tool{
- Type: "function",
- Function: models.ToolFunc{
- Name: "git",
- Description: "Execute read-only git commands. Allowed: status, log, diff, show, branch, reflog, rev-parse, shortlog, describe.",
- Parameters: models.ToolFuncParams{
- Type: "object",
- Required: []string{"subcommand"},
- Properties: map[string]models.ToolArgProps{
- "subcommand": models.ToolArgProps{
- Type: "string",
- Description: "git subcommand (status, log, diff, show, branch, reflog, rev-parse, shortlog, describe)",
- },
- },
- },
- },
- },
}
diff --git a/tools/chain.go b/tools/chain.go
index fb7767e..73ab6cd 100644
--- a/tools/chain.go
+++ b/tools/chain.go
@@ -266,6 +266,18 @@ func execBuiltin(name string, args []string, stdin string) string {
}
fsRootDir = abs
return fmt.Sprintf("Changed directory to: %s", fsRootDir)
+ case "go":
+ // Allow all go subcommands
+ if len(args) == 0 {
+ return "[error] usage: go <subcommand> [options]"
+ }
+ cmd := exec.Command("go", args...)
+ cmd.Dir = fsRootDir
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Sprintf("[error] go %s: %v\n%s", args[0], err, string(output))
+ }
+ return string(output)
}
return ""
}
diff --git a/tools/fs.go b/tools/fs.go
index 50b8da2..9fc09bb 100644
--- a/tools/fs.go
+++ b/tools/fs.go
@@ -614,6 +614,80 @@ func FsCd(args []string, stdin string) string {
return fmt.Sprintf("Changed directory to: %s", fsRootDir)
}
+func FsSed(args []string, stdin string) string {
+ if len(args) == 0 {
+ return "[error] usage: sed 's/old/new/[g]' [file]"
+ }
+
+ inPlace := false
+ var filePath string
+ var pattern string
+
+ for _, a := range args {
+ if a == "-i" || a == "--in-place" {
+ inPlace = true
+ } else if strings.HasPrefix(a, "s") && len(a) > 1 {
+ // This looks like a sed pattern
+ pattern = a
+ } else if filePath == "" && !strings.HasPrefix(a, "-") {
+ filePath = a
+ }
+ }
+
+ if pattern == "" {
+ return "[error] usage: sed 's/old/new/[g]' [file]"
+ }
+
+ // Parse pattern: s/old/new/flags
+ parts := strings.Split(pattern[1:], "/")
+ if len(parts) < 2 {
+ return "[error] invalid sed pattern. Use: s/old/new/[g]"
+ }
+
+ oldStr := parts[0]
+ newStr := parts[1]
+ global := len(parts) >= 3 && strings.Contains(parts[2], "g")
+
+ var content string
+ if filePath != "" && stdin == "" {
+ // Read from file
+ abs, err := resolvePath(filePath)
+ if err != nil {
+ return fmt.Sprintf("[error] sed: %v", err)
+ }
+ data, err := os.ReadFile(abs)
+ if err != nil {
+ return fmt.Sprintf("[error] sed: %v", err)
+ }
+ content = string(data)
+ } else if stdin != "" {
+ // Use stdin
+ content = stdin
+ } else {
+ return "[error] sed: no input (use file path or pipe from stdin)"
+ }
+
+ // Apply sed replacement
+ if global {
+ content = strings.ReplaceAll(content, oldStr, newStr)
+ } else {
+ content = strings.Replace(content, oldStr, newStr, 1)
+ }
+
+ if inPlace && filePath != "" {
+ abs, err := resolvePath(filePath)
+ if err != nil {
+ return fmt.Sprintf("[error] sed: %v", err)
+ }
+ if err := os.WriteFile(abs, []byte(content), 0644); err != nil {
+ return fmt.Sprintf("[error] sed: %v", err)
+ }
+ return fmt.Sprintf("Modified %s", filePath)
+ }
+
+ return content
+}
+
func FsMemory(args []string, stdin string) string {
if len(args) == 0 {
return "[error] usage: memory store <topic> <data> | memory get <topic> | memory list | memory forget <topic>"