summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools.go27
-rw-r--r--tools/chain.go77
-rw-r--r--tools/fs.go80
3 files changed, 165 insertions, 19 deletions
diff --git a/tools.go b/tools.go
index 1a2f095..ad245a2 100644
--- a/tools.go
+++ b/tools.go
@@ -518,9 +518,18 @@ func runCmd(args map[string]string) []byte {
case "capture_and_view", "screenshot_and_view":
// capture and view screenshot
return captureWindowAndView(args)
+ case "view_img":
+ // view_img <file> - view image for multimodal
+ return []byte(tools.FsViewImg(rest, ""))
case "browser":
// browser <action> [args...] - Playwright browser automation
return runBrowserCommand(rest, args)
+ case "mkdir", "ls", "cat", "pwd", "cd", "cp", "mv", "rm", "sed", "grep", "head", "tail", "wc", "sort", "uniq", "echo", "time", "stat", "go", "find", "file":
+ // File operations and shell commands - use ExecChain which has whitelist
+ return executeCommand(args)
+ case "git":
+ // git has its own whitelist in FsGit
+ return []byte(tools.FsGit(rest, ""))
default:
// Unknown subcommand - return help to remind user of available commands
return []byte(getHelp(nil))
@@ -667,13 +676,13 @@ func getHelp(args []string) string {
# File operations
ls [path] - list files in directory
cat <file> - read file content
- see <file> - view image file
+ view_img <file> - view image file
write <file> - write content to file
stat <file> - get file info
rm <file> - delete file
cp <src> <dst> - copy file
mv <src> <dst> - move/rename file
- mkdir <dir> - create directory
+ mkdir [-p] <dir> - create directory (use full path)
pwd - print working directory
cd <dir> - change directory
sed 's/old/new/[g]' [file] - text replacement
@@ -747,12 +756,12 @@ Use: run "command" to execute.`
Examples:
run "cat readme.md"
run "cat -b image.png" (base64 output)`
- case "see":
- return `see <image-file>
+ case "view_img":
+ return `view_img <image-file>
View an image file for multimodal analysis.
Supports: png, jpg, jpeg, gif, webp, svg
Example:
- run "see screenshot.png"`
+ run "view_img screenshot.png"`
case "write":
return `write <file> [content]
Write content to a file.
@@ -812,6 +821,14 @@ Use: run "command" to execute.`
Print working directory.
Example:
run "pwd"`
+ case "mkdir":
+ return `mkdir [-p] <directory>
+ Create a directory (use full path).
+ Options:
+ -p, --parents create parent directories as needed
+ Examples:
+ run "mkdir /full/path/myfolder"
+ run "mkdir -p /full/path/to/nested/folder"`
case "sed":
return `sed 's/old/new/[g]' [file]
Stream editor for text replacement.
diff --git a/tools/chain.go b/tools/chain.go
index 73ab6cd..4afb7e5 100644
--- a/tools/chain.go
+++ b/tools/chain.go
@@ -239,7 +239,12 @@ func execBuiltin(name string, args []string, stdin string) string {
}
return ""
}
- data, err := os.ReadFile(args[0])
+ path := args[0]
+ abs := path
+ if !filepath.IsAbs(path) {
+ abs = filepath.Join(fsRootDir, path)
+ }
+ data, err := os.ReadFile(abs)
if err != nil {
return fmt.Sprintf("[error] cat: %v", err)
}
@@ -266,6 +271,76 @@ func execBuiltin(name string, args []string, stdin string) string {
}
fsRootDir = abs
return fmt.Sprintf("Changed directory to: %s", fsRootDir)
+ case "mkdir":
+ if len(args) == 0 {
+ return "[error] usage: mkdir [-p] <dir>"
+ }
+ createParents := false
+ var dirPath string
+ for _, a := range args {
+ if a == "-p" || a == "--parents" {
+ createParents = true
+ } else if dirPath == "" {
+ dirPath = a
+ }
+ }
+ if dirPath == "" {
+ return "[error] usage: mkdir [-p] <dir>"
+ }
+ abs := dirPath
+ if !filepath.IsAbs(dirPath) {
+ abs = filepath.Join(fsRootDir, dirPath)
+ }
+ abs = filepath.Clean(abs)
+ var mkdirFunc func(string, os.FileMode) error
+ if createParents {
+ mkdirFunc = os.MkdirAll
+ } else {
+ mkdirFunc = os.Mkdir
+ }
+ if err := mkdirFunc(abs, 0o755); err != nil {
+ return fmt.Sprintf("[error] mkdir: %v", err)
+ }
+ if createParents {
+ return fmt.Sprintf("Created %s (with parents)", dirPath)
+ }
+ return fmt.Sprintf("Created %s", dirPath)
+ case "ls":
+ dir := "."
+ for _, a := range args {
+ if !strings.HasPrefix(a, "-") {
+ dir = a
+ break
+ }
+ }
+ abs := dir
+ if !filepath.IsAbs(dir) {
+ abs = filepath.Join(fsRootDir, dir)
+ }
+ entries, err := os.ReadDir(abs)
+ if err != nil {
+ return fmt.Sprintf("[error] ls: %v", err)
+ }
+ var out strings.Builder
+ for _, e := range entries {
+ info, _ := e.Info()
+ if e.IsDir() {
+ fmt.Fprintf(&out, "d %-8s %s/\n", "-", e.Name())
+ } else if info != nil {
+ size := info.Size()
+ sizeStr := fmt.Sprintf("%d", size)
+ if size > 1024 {
+ sizeStr = fmt.Sprintf("%.1fKB", float64(size)/1024)
+ }
+ fmt.Fprintf(&out, "f %-8s %s\n", sizeStr, e.Name())
+ } else {
+ fmt.Fprintf(&out, "f %-8s %s\n", "?", e.Name())
+ }
+ }
+ if out.Len() == 0 {
+ return "(empty directory)"
+ }
+ return strings.TrimRight(out.String(), "\n")
case "go":
// Allow all go subcommands
if len(args) == 0 {
diff --git a/tools/fs.go b/tools/fs.go
index 9fc09bb..f0368f6 100644
--- a/tools/fs.go
+++ b/tools/fs.go
@@ -2,7 +2,9 @@ package tools
import (
"encoding/base64"
+ "encoding/json"
"fmt"
+ "gf-lt/models"
"os"
"os/exec"
"path/filepath"
@@ -155,27 +157,53 @@ func FsCat(args []string, stdin string) string {
return string(data)
}
-func FsSee(args []string, stdin string) string {
+func FsViewImg(args []string, stdin string) string {
if len(args) == 0 {
- return "[error] usage: see <image-path>"
+ return "[error] usage: view_img <image-path>"
}
path := args[0]
- abs, err := resolvePath(path)
- if err != nil {
- return fmt.Sprintf("[error] %v", err)
+ var abs string
+ if filepath.IsAbs(path) {
+ abs = path
+ } else {
+ var err error
+ abs, err = resolvePath(path)
+ if err != nil {
+ return fmt.Sprintf("[error] %v", err)
+ }
}
- info, err := os.Stat(abs)
- if err != nil {
- return fmt.Sprintf("[error] see: %v", err)
+ if _, err := os.Stat(abs); err != nil {
+ return fmt.Sprintf("[error] view_img: %v", err)
}
if !IsImageFile(path) {
return fmt.Sprintf("[error] not an image file: %s (use cat to read text files)", path)
}
- return fmt.Sprintf("Image: %s (%s)\n![image](file://%s)", path, humanSize(info.Size()), abs)
+ dataURL, err := models.CreateImageURLFromPath(abs)
+ if err != nil {
+ return fmt.Sprintf("[error] view_img: %v", err)
+ }
+
+ result := models.MultimodalToolResp{
+ Type: "multimodal_content",
+ Parts: []map[string]string{
+ {"type": "text", "text": "Image: " + path},
+ {"type": "image_url", "url": dataURL},
+ },
+ }
+ jsonResult, err := json.Marshal(result)
+ if err != nil {
+ return fmt.Sprintf("[error] view_img: %v", err)
+ }
+ return string(jsonResult)
+}
+
+// FsSee is deprecated, use FsViewImg
+func FsSee(args []string, stdin string) string {
+ return FsViewImg(args, stdin)
}
func FsWrite(args []string, stdin string) string {
@@ -352,18 +380,44 @@ func FsMv(args []string, stdin string) string {
func FsMkdir(args []string, stdin string) string {
if len(args) == 0 {
- return "[error] usage: mkdir <dir>"
+ return "[error] usage: mkdir [-p] <dir>"
}
- abs, err := resolvePath(args[0])
+ createParents := false
+ var dirPath string
+
+ for _, a := range args {
+ if a == "-p" || a == "--parents" {
+ createParents = true
+ } else if dirPath == "" {
+ dirPath = a
+ }
+ }
+
+ if dirPath == "" {
+ return "[error] usage: mkdir [-p] <dir>"
+ }
+
+ abs, err := resolvePath(dirPath)
if err != nil {
return fmt.Sprintf("[error] %v", err)
}
- if err := os.MkdirAll(abs, 0o755); err != nil {
+ var mkdirFunc func(string, os.FileMode) error
+ if createParents {
+ mkdirFunc = os.MkdirAll
+ } else {
+ mkdirFunc = os.Mkdir
+ }
+
+ if err := mkdirFunc(abs, 0o755); err != nil {
return fmt.Sprintf("[error] mkdir: %v", err)
}
- return fmt.Sprintf("Created %s", args[0])
+
+ if createParents {
+ return fmt.Sprintf("Created %s (with parents)", dirPath)
+ }
+ return fmt.Sprintf("Created %s", dirPath)
}
// Text processing commands