Fix: view_img, exec commands

This commit is contained in:
Grail Finder
2026-03-14 16:34:58 +03:00
parent fdcaa6c5e2
commit 26377702d3
3 changed files with 165 additions and 19 deletions

View File

@@ -518,9 +518,18 @@ func runCmd(args map[string]string) []byte {
case "capture_and_view", "screenshot_and_view": case "capture_and_view", "screenshot_and_view":
// capture and view screenshot // capture and view screenshot
return captureWindowAndView(args) return captureWindowAndView(args)
case "view_img":
// view_img <file> - view image for multimodal
return []byte(tools.FsViewImg(rest, ""))
case "browser": case "browser":
// browser <action> [args...] - Playwright browser automation // browser <action> [args...] - Playwright browser automation
return runBrowserCommand(rest, args) 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: default:
// Unknown subcommand - return help to remind user of available commands // Unknown subcommand - return help to remind user of available commands
return []byte(getHelp(nil)) return []byte(getHelp(nil))
@@ -667,13 +676,13 @@ func getHelp(args []string) string {
# File operations # File operations
ls [path] - list files in directory ls [path] - list files in directory
cat <file> - read file content cat <file> - read file content
see <file> - view image file view_img <file> - view image file
write <file> - write content to file write <file> - write content to file
stat <file> - get file info stat <file> - get file info
rm <file> - delete file rm <file> - delete file
cp <src> <dst> - copy file cp <src> <dst> - copy file
mv <src> <dst> - move/rename file mv <src> <dst> - move/rename file
mkdir <dir> - create directory mkdir [-p] <dir> - create directory (use full path)
pwd - print working directory pwd - print working directory
cd <dir> - change directory cd <dir> - change directory
sed 's/old/new/[g]' [file] - text replacement sed 's/old/new/[g]' [file] - text replacement
@@ -747,12 +756,12 @@ Use: run "command" to execute.`
Examples: Examples:
run "cat readme.md" run "cat readme.md"
run "cat -b image.png" (base64 output)` run "cat -b image.png" (base64 output)`
case "see": case "view_img":
return `see <image-file> return `view_img <image-file>
View an image file for multimodal analysis. View an image file for multimodal analysis.
Supports: png, jpg, jpeg, gif, webp, svg Supports: png, jpg, jpeg, gif, webp, svg
Example: Example:
run "see screenshot.png"` run "view_img screenshot.png"`
case "write": case "write":
return `write <file> [content] return `write <file> [content]
Write content to a file. Write content to a file.
@@ -812,6 +821,14 @@ Use: run "command" to execute.`
Print working directory. Print working directory.
Example: Example:
run "pwd"` 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": case "sed":
return `sed 's/old/new/[g]' [file] return `sed 's/old/new/[g]' [file]
Stream editor for text replacement. Stream editor for text replacement.

View File

@@ -239,7 +239,12 @@ func execBuiltin(name string, args []string, stdin string) string {
} }
return "" 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 { if err != nil {
return fmt.Sprintf("[error] cat: %v", err) return fmt.Sprintf("[error] cat: %v", err)
} }
@@ -266,6 +271,76 @@ func execBuiltin(name string, args []string, stdin string) string {
} }
fsRootDir = abs fsRootDir = abs
return fmt.Sprintf("Changed directory to: %s", fsRootDir) 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": case "go":
// Allow all go subcommands // Allow all go subcommands
if len(args) == 0 { if len(args) == 0 {

View File

@@ -2,7 +2,9 @@ package tools
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"gf-lt/models"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -155,27 +157,53 @@ func FsCat(args []string, stdin string) string {
return string(data) return string(data)
} }
func FsSee(args []string, stdin string) string { func FsViewImg(args []string, stdin string) string {
if len(args) == 0 { if len(args) == 0 {
return "[error] usage: see <image-path>" return "[error] usage: view_img <image-path>"
} }
path := args[0] path := args[0]
abs, err := resolvePath(path) var abs string
if err != nil { if filepath.IsAbs(path) {
return fmt.Sprintf("[error] %v", err) 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 := os.Stat(abs); err != nil {
if err != nil { return fmt.Sprintf("[error] view_img: %v", err)
return fmt.Sprintf("[error] see: %v", err)
} }
if !IsImageFile(path) { if !IsImageFile(path) {
return fmt.Sprintf("[error] not an image file: %s (use cat to read text files)", 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 { 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 { func FsMkdir(args []string, stdin string) string {
if len(args) == 0 { 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 { if err != nil {
return fmt.Sprintf("[error] %v", err) 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("[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 // Text processing commands