|
17 | 17 | [["path" fs/exists? "$path is not a valid path"] |
18 | 18 | ["path" (partial allowed-path? db) (str "Access denied - path $path outside allowed directories: " (tools.util/workspace-roots-strs db))]]) |
19 | 19 |
|
20 | | -(defn ^:private list-directory [arguments {:keys [db]}] |
| 20 | +(defn ^:private directory-tree [arguments {:keys [db]}] |
21 | 21 | (let [path (delay (fs/canonicalize (get arguments "path")))] |
22 | 22 | (or (tools.util/invalid-arguments arguments (path-validations db)) |
23 | 23 | (tools.util/single-text-content |
|
165 | 165 |
|
166 | 166 | (defn ^:private edit-file [arguments {:keys [db]}] |
167 | 167 | (or (tools.util/invalid-arguments arguments (concat (path-validations db) |
168 | | - [["path" fs/readable? "File $path is not readable"] |
169 | | - ["start_line" #(and (integer? %) (pos? %)) "$start_line must be a positive integer"] |
170 | | - ["end_line" #(and (integer? %) (pos? %)) "$end_line must be a positive integer"] |
171 | | - ["content" #(string? %) "$content must be a string"]])) |
| 168 | + [["path" fs/readable? "File $path is not readable"]])) |
172 | 169 | (let [path (get arguments "path") |
173 | | - start-line (dec (int (get arguments "start_line"))) ; convert 1-based to 0-based |
174 | | - end-line (dec (int (get arguments "end_line"))) ; inclusive, 0-based |
175 | | - new-lines (string/split-lines (get arguments "content")) |
176 | | - old-lines (vec (string/split-lines (slurp path))) |
177 | | - before (subvec old-lines 0 start-line) |
178 | | - after (subvec old-lines (inc end-line)) |
179 | | - new-content-lines (vec (concat before new-lines after)) |
180 | | - new-file-content (string/join "\n" new-content-lines)] |
181 | | - (spit path new-file-content) |
182 | | - (tools.util/single-text-content (format "Successfully replaced lines %d-%d in %s." (inc start-line) (inc end-line) path))))) |
| 170 | + original-content (get arguments "original_content") |
| 171 | + new-content (get arguments "new_content") |
| 172 | + all? (boolean (get arguments "all_occurrences")) |
| 173 | + content (slurp path)] |
| 174 | + (if (string/includes? content original-content) |
| 175 | + (let [content (if all? |
| 176 | + (string/replace content original-content new-content) |
| 177 | + (string/replace-first content original-content new-content))] |
| 178 | + (spit path content) |
| 179 | + (tools.util/single-text-content (format "Successfully replaced content in %s." path))) |
| 180 | + (tools.util/single-text-content (format "Original content not found in %s" path) :error))))) |
183 | 181 |
|
184 | 182 | (defn ^:private move-file [arguments {:keys [db]}] |
185 | 183 | (let [workspace-dirs (tools.util/workspace-roots-strs db)] |
|
193 | 191 | (tools.util/single-text-content (format "Successfully moved %s to %s" source destination)))))) |
194 | 192 |
|
195 | 193 | (def definitions |
196 | | - {"eca_list_directory" |
197 | | - {:description (str "Get a detailed listing of all files and directories in a specified path. " |
198 | | - "Results clearly distinguish between files and directories with [FILE] and [DIR] " |
199 | | - "prefixes. This tool is essential for understanding directory structure and " |
200 | | - "finding specific files within a directory." |
| 194 | + {"eca_directory_tree" |
| 195 | + {:description (str "Returns a recursive tree view of files and directories starting from the specified path. " |
| 196 | + "The path parameter must be an absolute path, not a relative path. " |
201 | 197 | "**Only works within the directories: $workspaceRoots.**") |
202 | 198 | :parameters {:type "object" |
203 | 199 | :properties {"path" {:type "string" |
204 | | - :description "The absolute path to the directory to list."}} |
| 200 | + :description "The absolute path to the directory."} |
| 201 | + "max_depth" {:type "integer" |
| 202 | + :description "Maximum depth to traverse (optional)"} |
| 203 | + "limit" {:type "integer" |
| 204 | + :description "Maxium number of entries to show (default: 100)"}} |
205 | 205 | :required ["path"]} |
206 | | - :handler #'list-directory} |
| 206 | + :handler #'directory-tree} |
207 | 207 | "eca_read_file" |
208 | 208 | {:description (str "Read the contents of a file from the file system. " |
209 | 209 | "Use this tool when you need to examine " |
210 | 210 | "the contents of a single file. Optionally use the 'line_offset' and/or 'limit' " |
211 | | - "parameters to read specific contents of the file when you know the range." |
| 211 | + "parameters to read specific contents of the file when you know the range. " |
| 212 | + "Prefer call once this tool over multiple calls passing small offsets. " |
212 | 213 | "**Only works within the directories: $workspaceRoots.**") |
213 | 214 | :parameters {:type "object" |
214 | 215 | :properties {"path" {:type "string" |
|
230 | 231 | :required ["path" "content"]} |
231 | 232 | :handler #'write-file} |
232 | 233 | "eca_edit_file" |
233 | | - {:description (str "Change the specified file replacing the lines range with the given content. " |
234 | | - "Make sure to have the existing file content updated via your context or eca_read_file before calling this. " |
235 | | - "This can also be used to prepend, append, or delete contents from a file.") |
| 234 | + {:description (str "Replace a specific string or content block in a file with new content. " |
| 235 | + "Finds the exact original content and replaces it with new content. " |
| 236 | + "Be extra careful to format the original-content exactly correctly, " |
| 237 | + "taking extra care with whitespace and newlines. In addition to replacing strings, " |
| 238 | + "this can also be used to prepend, append, or delete contents from a file.") |
236 | 239 | :parameters {:type "object" |
237 | 240 | :properties {"path" {:type "string" |
238 | 241 | :description "The absolute file path to do the replace."} |
239 | | - "start_line" {:type "number" |
240 | | - :description "The 1-based start line number"} |
241 | | - "end_line" {:type "number" |
242 | | - :description "The 1-based end line number"} |
243 | | - "content" {:type "string" |
244 | | - :description "The new content to be inserted"}} |
245 | | - :required ["path" "start_line" "end_line" "content"]} |
| 242 | + "original_content" {:type "string" |
| 243 | + :description "The exact content to find and replace"} |
| 244 | + "new_content" {:type "string" |
| 245 | + :description "The new content to replace the original content with"} |
| 246 | + "all_occurrences" {:type "boolean" |
| 247 | + :description "Whether to replace all occurences of the file or just the first one (default)"}} |
| 248 | + :required ["path" "original_content" "new_content"]} |
246 | 249 | :handler #'edit-file} |
247 | 250 | "eca_move_file" |
248 | 251 | {:description (str "Move or rename files and directories. Can move files between directories " |
|
0 commit comments