tools

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BashTool = DefineTool("bash", "Execute a shell command in the current working directory. Returns combined stdout and stderr. Output is truncated to last max_lines lines (default: 500).",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"command":     map[string]any{"type": "string", "description": "The shell command to execute"},
			"description": map[string]any{"type": "string", "description": "Brief description of what this command does"},
			"max_lines":   map[string]any{"type": "integer", "description": "Maximum lines to return from the end of output (default: 500)"},
		},
		"required": []string{"command"},
	},
	func(ctx context.Context, p BashParams) (string, error) {
		script := "trap 'pkill -TERM -P $$ 2>/dev/null' EXIT\n" + p.Command
		scriptF, err := os.CreateTemp("", "mate-bash-*.sh")
		if err != nil {
			return "", fmt.Errorf("creating temp script: %w", err)
		}
		defer func() { _ = os.Remove(scriptF.Name()) }()
		if _, err := scriptF.WriteString(script); err != nil {
			_ = scriptF.Close()
			return "", fmt.Errorf("writing temp script: %w", err)
		}
		_ = scriptF.Close()

		outF, err := os.CreateTemp("", "mate-bash-out-*")
		if err != nil {
			return "", fmt.Errorf("creating output file: %w", err)
		}
		defer func() { _ = os.Remove(outF.Name()) }()

		cmd := exec.CommandContext(ctx, "bash", scriptF.Name())
		cmd.Stdout = outF
		cmd.Stderr = outF
		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
		cmd.Cancel = func() error {
			return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
		}

		runErr := cmd.Run()

		if cmd.Process != nil {
			pgid := cmd.Process.Pid
			_ = syscall.Kill(-pgid, syscall.SIGTERM)
			time.Sleep(20 * time.Millisecond)
			_ = syscall.Kill(-pgid, syscall.SIGKILL)
		}

		_ = outF.Close()
		outBytes, _ := os.ReadFile(outF.Name())
		result := string(outBytes)
		if runErr != nil {
			result = strings.TrimSpace(result)
			if result == "" {
				result = runErr.Error()
			}
		}
		result = strings.TrimSpace(result)
		if result == "" {
			return result, nil
		}

		maxLines := p.MaxLines
		if maxLines <= 0 {
			maxLines = 500
		}

		lines := strings.Split(result, "\n")
		if len(lines) <= maxLines {
			return result, nil
		}

		truncated := lines[len(lines)-maxLines:]
		return strings.Join(truncated, "\n"), nil
	},
)
View Source
var EditFileTool = DefineTool("edit_file", "Edit a file using exact text replacement. Each oldText must be unique in the file. Multiple edits are applied in order.",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path": map[string]any{"type": "string", "description": "Path to the file to edit (relative or absolute)"},
			"edits": map[string]any{
				"type": "array",
				"items": map[string]any{
					"type": "object",
					"properties": map[string]any{
						"oldText": map[string]any{"type": "string", "description": "Exact text to replace"},
						"newText": map[string]any{"type": "string", "description": "Replacement text"},
					},
					"required": []string{"oldText", "newText"},
				},
			},
		},
		"required": []string{"path", "edits"},
	},
	func(ctx context.Context, p EditParams) (string, error) {
		data, err := os.ReadFile(p.Path)
		if err != nil {
			return "", fmt.Errorf("read file %s: %w", p.Path, err)
		}

		content := string(data)
		applied := 0

		for _, edit := range p.Edits {
			count := strings.Count(content, edit.OldText)
			if count == 0 {
				return "", fmt.Errorf("oldText not found in %s: %q", p.Path, edit.OldText)
			}
			if count > 1 {
				return "", fmt.Errorf("oldText found %d times in %s, must be unique: %q", count, p.Path, edit.OldText)
			}
			content = strings.Replace(content, edit.OldText, edit.NewText, 1)
			applied++
		}

		if err := atomicWrite(p.Path, []byte(content)); err != nil {
			return "", fmt.Errorf("write file %s: %w", p.Path, err)
		}

		return fmt.Sprintf("Applied %d edit(s) to %s", applied, p.Path), nil
	},
)
View Source
var GlobTool = DefineTool("glob", "Find files matching a glob pattern. Supports ** for recursive matching. Skips common VCS/dependency directories and respects .gitignore.",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"pattern":     map[string]any{"type": "string", "description": "Glob pattern, e.g. \"**/*.go\", \"src/*_test.go\", \"*.md\""},
			"path":        map[string]any{"type": "string", "description": "Root directory to search from (default: current working directory)"},
			"max_results": map[string]any{"type": "integer", "description": "Maximum results (default: 50)"},
		},
		"required": []string{"pattern"},
	},
	func(ctx context.Context, p GlobParams) (string, error) {
		if p.Path == "" {
			p.Path = "."
		}
		if p.MaxResults <= 0 {
			p.MaxResults = 50
		}

		re, err := globToRegex(p.Pattern)
		if err != nil {
			return "", fmt.Errorf("glob: %w", err)
		}

		ig := ParseGitignore(p.Path)

		var results []string
		walkErr := filepath.WalkDir(p.Path, func(path string, d fs.DirEntry, err error) error {
			if err != nil {
				return nil
			}
			rel, _ := filepath.Rel(p.Path, path)
			if d.IsDir() {
				if shouldSkipDir(d.Name()) {
					return filepath.SkipDir
				}
				if ig.IsIgnored(rel, true) {
					return filepath.SkipDir
				}
				return nil
			}
			if ig.IsIgnored(rel, false) {
				return nil
			}
			if re.MatchString(rel) {
				results = append(results, rel)
				if len(results) >= p.MaxResults {
					return fs.SkipAll
				}
			}
			return nil
		})
		if walkErr != nil {
			return "", fmt.Errorf("glob: %w", walkErr)
		}

		sort.Strings(results)
		return strings.Join(results, "\n"), nil
	},
)
View Source
var GrepTool = DefineTool("grep", "Search for a pattern in files. Returns matching lines with file path and line number. Skips binary files and common VCS/dependency directories.",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"pattern":     map[string]any{"type": "string", "description": "Text or regex pattern to search for"},
			"path":        map[string]any{"type": "string", "description": "File or directory to search in (default: current working directory)"},
			"glob":        map[string]any{"type": "string", "description": "Filter file names, e.g. \"*.go\", \"*_test.go\""},
			"max_results": map[string]any{"type": "integer", "description": "Maximum matches to return (default: 30)"},
			"regex":       map[string]any{"type": "boolean", "description": "Treat pattern as regex (default: false, literal match)"},
		},
		"required": []string{"pattern"},
	},
	func(ctx context.Context, p GrepParams) (string, error) {
		if p.Path == "" {
			p.Path = "."
		}
		if p.MaxResults <= 0 {
			p.MaxResults = 30
		}

		matcher, err := buildMatcher(p.Pattern, p.Regex)
		if err != nil {
			return "", fmt.Errorf("grep: %w", err)
		}

		info, err := os.Stat(p.Path)
		if err != nil {
			return "", fmt.Errorf("grep: %w", err)
		}

		if !info.IsDir() {
			if isBinaryFile(p.Path) {
				return "", nil
			}
			return grepFile(p.Path, matcher, p.MaxResults)
		}

		ig := ParseGitignore(p.Path)

		var results []string
		walkErr := filepath.WalkDir(p.Path, func(path string, d fs.DirEntry, err error) error {
			if err != nil {
				return nil
			}
			if d.IsDir() {
				if shouldSkipDir(d.Name()) {
					return filepath.SkipDir
				}
				rel, _ := filepath.Rel(p.Path, path)
				if ig.IsIgnored(rel, true) {
					return filepath.SkipDir
				}
				return nil
			}
			rel, _ := filepath.Rel(p.Path, path)
			if ig.IsIgnored(rel, false) {
				return nil
			}
			if p.Glob != "" {
				matched, _ := filepath.Match(p.Glob, d.Name())
				if !matched {
					return nil
				}
			}
			if isBinaryFile(path) {
				return nil
			}
			matches, _ := grepFile(path, matcher, p.MaxResults-len(results))
			if matches != "" {
				results = append(results, matches)
			}
			if len(results) >= p.MaxResults {
				return fs.SkipAll
			}
			return nil
		})
		if walkErr != nil {
			return "", fmt.Errorf("grep: %w", walkErr)
		}

		return strings.Join(results, "\n"), nil
	},
)
View Source
var ReadFileTool = DefineTool("read_file", "Read contents of a file. Supports text files. When no offset/limit is specified, returns up to 500 lines. Use offset/limit for large files.",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path":   map[string]any{"type": "string", "description": "Path to the file to read (relative or absolute)"},
			"offset": map[string]any{"type": "integer", "description": "Line number to start reading from (1-indexed)"},
			"limit":  map[string]any{"type": "integer", "description": "Maximum number of lines to read"},
		},
		"required": []string{"path"},
	},
	func(ctx context.Context, p ReadFileParams) (string, error) {
		if p.Offset < 1 {
			p.Offset = 1
		}
		if p.Limit <= 0 {
			p.Limit = defaultReadLimit
		}

		content, _, err := readFileLines(p)
		if err != nil {
			return "", err
		}

		return content, nil
	},
)
View Source
var WriteFileTool = DefineTool("write_file", "Create or overwrite a file with the given content. Creates parent directories automatically.",
	map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path":    map[string]any{"type": "string", "description": "Path to the file to write (relative or absolute)"},
			"content": map[string]any{"type": "string", "description": "Content to write to the file"},
		},
		"required": []string{"path", "content"},
	},
	func(ctx context.Context, p WriteFileParams) (string, error) {
		dir := filepath.Dir(p.Path)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return "", fmt.Errorf("create parent directories for %s: %w", p.Path, err)
		}
		if err := atomicWrite(p.Path, []byte(p.Content)); err != nil {
			return "", fmt.Errorf("write file %s: %w", p.Path, err)
		}
		return fmt.Sprintf("Wrote %d bytes to %s", len(p.Content), p.Path), nil
	},
)

Functions

func CatalogNames

func CatalogNames() []string

func Register

func Register(name string, t Tool)

func Standard

func Standard() map[string]Tool

Types

type BashParams

type BashParams struct {
	Command     string `json:"command"`
	Description string `json:"description,omitempty"`
	MaxLines    int    `json:"max_lines,omitempty"`
}

type EditOp

type EditOp struct {
	OldText string `json:"oldText"`
	NewText string `json:"newText"`
}

type EditParams

type EditParams struct {
	Path  string   `json:"path"`
	Edits []EditOp `json:"edits"`
}

type GitignoreMatcher

type GitignoreMatcher struct {
	// contains filtered or unexported fields
}

func ParseGitignore

func ParseGitignore(root string) *GitignoreMatcher

func (*GitignoreMatcher) IsIgnored

func (m *GitignoreMatcher) IsIgnored(relPath string, isDir bool) bool

type GitignoreRule

type GitignoreRule struct {
	// contains filtered or unexported fields
}

type GlobParams

type GlobParams struct {
	Pattern    string `json:"pattern"`
	Path       string `json:"path,omitempty"`
	MaxResults int    `json:"max_results,omitempty"`
}

type GrepParams

type GrepParams struct {
	Pattern    string `json:"pattern"`
	Path       string `json:"path,omitempty"`
	Glob       string `json:"glob,omitempty"`
	MaxResults int    `json:"max_results,omitempty"`
	Regex      bool   `json:"regex,omitempty"`
}

type ReadFileParams

type ReadFileParams struct {
	Path   string `json:"path"`
	Offset int    `json:"offset"`
	Limit  int    `json:"limit"`
}

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

func NewRegistry

func NewRegistry() *Registry

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, bool)

func (*Registry) Names

func (r *Registry) Names() []string

func (*Registry) Register

func (r *Registry) Register(tool Tool) error

func (*Registry) ToolDefs

func (r *Registry) ToolDefs() []message.ToolDef

type Tool

type Tool struct {
	Name        string
	Description string
	Parameters  map[string]any
	Execute     func(ctx context.Context, rawParams json.RawMessage) (string, error)
}

func DefineTool

func DefineTool[P any](name, description string, paramsSchema map[string]any, fn func(ctx context.Context, p P) (string, error)) Tool

func Lookup

func Lookup(name string) (Tool, bool)

type WriteFileParams

type WriteFileParams struct {
	Path    string `json:"path"`
	Content string `json:"content"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL