commit ecf082bd0a47da1436e4f8b482f6b92f10f0b924
parent 137bec6d0706cfc341f99b12da52bb6932508580
Author: Vineet Kumar <git@vineetk.net>
Date: Sun, 24 May 2026 15:42:02 -0400
add confirm predicate to bash tool
Diffstat:
| M | llm-tools.el | | | 52 | +++++++++++++++++++++++++++++++--------------------- |
1 file changed, 31 insertions(+), 21 deletions(-)
diff --git a/llm-tools.el b/llm-tools.el
@@ -59,12 +59,7 @@ final file."))
;; TODO edit_file (hashline + patch apply)
-;; TODO bash_readonly (shell-command-to-string, whitelisted commands)
-;; TODO compile command (calls build system, doesn't need confirmation, whitelisted commands)
-
;; bash (make-process, taken from gptel-agent)
-;; TODO refuse destructive operations like rm and dd
-;; TODO refuse operations that have dedicated tool definitions like grep and compile
(defun llm-tools--execute-bash (callback command)
"Execute COMMAND asynchronously in bash and call CALLBACK with output.
@@ -90,6 +85,24 @@ COMMAND is the bash command string to execute."
exit-code output)))))))))
proc))
+(defun llm-tools--bash-confirm-p (command)
+ (let ((destructive
+ '("\\brm\\b"
+ "\\bdd\\b"
+ "\\bshred\\b"
+ "\\btruncate\\b"
+ "\\bmkfs\\b"
+ "\\bsudo\\b"
+ "\\bdoas\\b"
+ "[^>]>[^>]" ; any singular > that isn't >>
+ "\\$("
+ "`"
+ "|[ \t]*\\bsh\\b"
+ "|[ \t]*\\bbash\\b")))
+ (cl-some (lambda (regexp)
+ (string-match-p regexp command))
+ destructive)))
+
(gptel-make-tool
:name "bash"
:function #'llm-tools--execute-bash
@@ -99,10 +112,8 @@ This tool provides access to a Bash shell with GNU coreutils (or
equivalents) available. Use this to inspect system state, run builds,
tests or other development or system administration tasks.
-TODO replace with saying do not use bash for operations that have equivalent tool definitions
-Do NOT use this for file operations, finding, reading or editing files.
-Use the provided file tools instead: `read_file`, `write_file`,
-`edit_file`, `find_file`, `grep`.
+Use the dedicated tools for file operations, searching, and code
+navigation instead of shell commands.
- Quote file paths with spaces using double quotes.
- Chain dependent commands with && (or ; if failures are OK)
@@ -121,18 +132,18 @@ returned as a string. Long outputs should be filtered/limited using pipes."
:description "The Bash command to execute. \
Can include pipes and standard shell operators.
Example: 'ls -la | head -20' or 'grep -i error app.log | tail -50'"))
- :confirm t
+ :confirm #'llm-tools--bash-confirm-p
:include t
:async t)
;; find_files (directory-files-recursively)
(gptel-make-tool
:name "find_files"
- :function (lambda (dir &optional pattern)
+ :function (lambda (dir &optional regexp)
(let ((git-dir (concat (file-name-as-directory dir) ".git")))
(delq nil (mapcar (lambda (f)
(unless (string-prefix-p git-dir f) f))
- (directory-files-recursively dir (or pattern ""))))))
+ (directory-files-recursively dir (or regexp ""))))))
:description "\
Recursively find all files in a given directory, excluding files within
.git directories. Returns a list of absolute file paths. Optionally
@@ -142,9 +153,9 @@ or editing them."
:args '((:name "directory"
:type string
:description "Path to the directory to be read, relative to the working directory.")
- (:name "pattern"
+ (:name "regexp"
:type string
- :description "Optional file name pattern to filter results, e.g. '*.el' or '*.txt'."
+ :description "Optional regular expression pattern to search for, uses extended regex syntax."
:optional t))
:async nil
:confirm nil
@@ -153,25 +164,24 @@ or editing them."
;; grep (just grep (or git grep) :D)
(gptel-make-tool
:name "grep"
- :function (lambda (regex &optional path)
+ :function (lambda (regexp &optional path)
(with-temp-buffer
(if (magit-gitdir)
(call-process "git" nil t nil
- "--no-pager"
"grep"
"-rnEH"
- regex
+ regexp
(or path "."))
(call-process "grep" nil t nil
"-rnEH"
- regex
+ regexp
(or path ".")))
(buffer-string)))
:description "\
-Recursively search for lines matching a regex pattern in files under a
+Recursively search for lines matching a regexp pattern in files under a
given path. Uses `git grep` when inside a Git repository, otherwise falls
back to GNU grep. Both are invoked with flags -rnEH (recursive, line
-numbers, extended regex, always show filename). When using `git grep`,
+numbers, extended regexp, always show filename). When using `git grep`,
only tracked files are searched. Returns raw output with one match per
line in the format FILE:LINE:CONTENT. Use this tool to find code
patterns, locate function definitions, search for strings, or identify
@@ -179,7 +189,7 @@ files containing specific text before reading them. For simple file name
discovery, use `find_files` instead. For reading file contents, use
`read_file`. The path argument is relative to the working directory;
omit it to search from the current directory."
- :args '((:name "regex"
+ :args '((:name "regexp"
:type string
:description "\
Regular expression pattern to search for. Uses GNU grep extended syntax.")