// Package tools holds the plugin's chat tools. package tools import ( "bytes" "context" "fmt" "os" "[[.SDKModule]]/pluginsdk" ) // All returns every chat tool. Each is also listed in plugin.json. func All() []pluginsdk.Tool { return []pluginsdk.Tool{countLines, saveSummary} } var pathParam = map[string]any{ "type": "object", "properties": map[string]any{ "path": map[string]any{"type": "string", "description": "The text file, as the user named it."}, }, "required": []string{"path"}, } var countLines = pluginsdk.Tool{ Name: "[[.Short]]_count_lines", Description: "Count the lines in a text file. Use when the user asks how long a text file is. Returns the line count.", Parameters: pathParam, Run: func(ctx context.Context, h *pluginsdk.Host, call pluginsdk.ToolCall) pluginsdk.ToolResult { path, lines, err := count(ctx, h, call) if err != nil { return pluginsdk.Fail("%v", err) } return pluginsdk.Text("%s has %d lines.", path, lines) }, } var saveSummary = pluginsdk.Tool{ Name: "[[.Short]]_save_summary", Description: "Save a one-line summary (the line count) next to a text file, as .summary.txt. Use only when the user asks to save it.", Parameters: pathParam, Mutating: true, Run: func(ctx context.Context, h *pluginsdk.Host, call pluginsdk.ToolCall) pluginsdk.ToolResult { path, lines, err := count(ctx, h, call) if err != nil { return pluginsdk.Fail("%v", err) } out := path + ".summary.txt" allowed, err := h.CanWrite(ctx, out) if err != nil { return pluginsdk.Fail("%v", err) } if !allowed || !call.EditMode { return pluginsdk.Fail("Cletus doesn't allow edits in this folder. Ask the user to allow edits, then try again.") } if err := os.WriteFile(out, []byte(fmt.Sprintf("%d lines\n", lines)), 0o644); err != nil { return pluginsdk.Fail("Couldn't save the summary: %v", err) } return pluginsdk.Text("Saved %s.", out) }, } // count resolves the path through the host and counts its lines. func count(ctx context.Context, h *pluginsdk.Host, call pluginsdk.ToolCall) (string, int, error) { var args struct { Path string `json:"path"` } if err := call.Decode(&args); err != nil { return "", 0, err } if args.Path == "" { return "", 0, fmt.Errorf("give the path of a text file") } path, err := h.ResolvePath(ctx, args.Path) if err != nil { return "", 0, err } data, err := os.ReadFile(path) if err != nil { return "", 0, fmt.Errorf("couldn't read %s: %v", args.Path, err) } lines := bytes.Count(data, []byte("\n")) if len(data) > 0 && data[len(data)-1] != '\n' { lines++ } return path, lines, nil }