From 44d56bdefd34c40e7c9428dc1d42ca595b641082 Mon Sep 17 00:00:00 2001
From: Phil Bajsicki <phil@bajsicki.com>
Date: Tue, 15 Apr 2025 21:17:23 +0200
Subject: [PATCH] Fix org-ql-select-rifle

---
 README.org         | 471 +++++++++++++++++++++++----------------------
 gptel-org-tools.el | 412 +++++++++++++++++++--------------------
 2 files changed, 451 insertions(+), 432 deletions(-)

diff --git a/README.org b/README.org
index f94b764..25a3680 100644
--- a/README.org
+++ b/README.org
@@ -57,15 +57,15 @@ I change models a lot, and this /just works/ for most models, even if some aren'
 (setq gptel-model 'llamacpp)
 (setq gptel-include-reasoning t)
 (setq gptel-backend (gptel-make-deepseek "llamacpp"
-		 :host "localhost:8080"
-		 :protocol "http"
-		 :stream nil
-		 :models '("llamacpp"
-			   :capabilities (reasoning))
-		 :request-params '(:thinking t
-				   :enable_thinking t
-				   :include_reasoning t
-				   :parallel_tool_calls t)))
+		      :host "localhost:8080"
+		      :protocol "http"
+		      :stream nil
+		      :models '("llamacpp"
+				:capabilities (reasoning))
+		      :request-params '(:thinking t
+					:enable_thinking t
+					:include_reasoning t
+					:parallel_tool_calls t)))
 
 (setf (alist-get 'org-mode gptel-prompt-prefix-alist) "@user\n")
 (setf (alist-get 'org-mode gptel-response-prefix-alist) "@assistant\n")
@@ -120,6 +120,12 @@ Stuff, headers, etc.
 
 ;;; Code:)
 #+end_src
+** gptel-org-tools
+Collects into =gptel-org-tools= list, distinct from =gptel-tools=
+#+begin_src elisp
+(defvar gptel-org-tools '())
+#+end_src
+
 ** The tools
 *** Emacs
 These tools are primarily concerned with Emacs, Emacs Lisp, and files-or-buffers.
@@ -133,19 +139,20 @@ E.g. can't find the right ~org-ql-select~ query? Let's use an eldritch abominati
 Highly not recommended, but sometimes an LLM can pull a rabbit out of pure entropy.
 
 #+begin_src elisp :tangle no
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (elisp)
-             (unless (stringp elisp) (error "elisp code must be a string"))
-             (with-temp-buffer
-               (insert (eval (read elisp)))
-               (buffer-string)))
- :name "eval"
- :description "Execute arbitrary Emacs Lisp code"
- :args (list '(:name "eval"
-               :type string
-               :description "The Emacs Lisp code to evaluate."))
- :category "emacs"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (elisp)
+			  (unless (stringp elisp) (error "elisp code must be a string"))
+			  (with-temp-buffer
+			    (insert (eval (read elisp)))
+			    (buffer-string)))
+	      :name "eval"
+	      :description "Execute arbitrary Emacs Lisp code"
+	      :args (list '(:name "eval"
+			    :type string
+			    :description "The Emacs Lisp code to evaluate."))
+	      :category "emacs"
+	      :confirm t))
 #+end_src
 
 **** list-buffers
@@ -155,21 +162,21 @@ The rationale behind using ~ibuffer~ is the same as with dired. They both displa
 
 Seems to be one of the most reliable tools in the basket... mostly because
 #+begin_src elisp
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (arg)
-	     (with-temp-buffer
-	       (ibuffer)
-	       (let ((content (buffer-string)))
-	  (kill-buffer (current-buffer))
-	  content)))
- :name "list-buffers"
- :description "Access the list of buffers open in Emacs, including file names and full paths."
- :args (list '(:name "arg"
-	       :type string
-	       :description "Does nothing."
-	       :optional t))
- :category "emacs"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (arg)
+			  (with-temp-buffer
+			    (ibuffer)
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "list-buffers"
+	      :description "Access the list of buffers open in Emacs, including file names and full paths."
+	      :args (list '(:name "arg"
+			    :type string
+			    :description "Does nothing."
+			    :optional t))
+	      :category "emacs"))
 #+end_src
 **** dired
 See above, same reasoning. There's very little reason to use the ~directory-files~ function for this (as in another tool I saw.) The reason is, ~directory-files~ doesn't provide nearly as much information about the items in that directory. Not even a distinction between files and directories.
@@ -181,54 +188,57 @@ Be sure to customize the function to point to your org directory, if you wish. I
 #+end_comment
 
 #+begin_src elisp
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (dir)
-	     (with-temp-buffer
-	       (dired (or dir "~"))
-	       (let ((content (buffer-string)))
-		 (kill-buffer (current-buffer))
-		 content)))
- :name "dired"
- :description "List directory contents"
- :args (list '(:name "dir"
-               :type string
-               :description "Directory path"
-	       :optional t))
- :category "filesystem"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (dir)
+			  (with-temp-buffer
+			    (dired (or dir "~"))
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "dired"
+	      :description "List directory contents"
+	      :args (list '(:name "dir"
+			    :type string
+			    :description "Directory path"
+			    :optional t))
+	      :category "filesystem"))
 #+end_src
 
 **** find-buffer-visiting
 #+begin_src elisp
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (filename)
-	     (bufferp (find-buffer-visiting (expand-file-name filename))))
- :name "find-buffer-visiting"
- :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
- :args (list '(:name "filename"
-	       :type string
-	       :description "The filename to compare to open buffers."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (filename)
+			  (bufferp (find-buffer-visiting (expand-file-name filename))))
+	      :name "find-buffer-visiting"
+	      :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
+	      :args (list '(:name "filename"
+			    :type string
+			    :description "The filename to compare to open buffers."))
+	      :category "org-mode"))
 #+end_src
-**** find-file-noselect
-Continuation from above. Open a file into a buffer for processing. Onec it's found by dired-list.
+**** open-file-inactive
+Continuation from above. Open a file into a buffer for processing, once it's found by dired-list.
 #+begin_src elisp
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (file)
-      (find-file-noselect file))
- :name "find-file-noselect"
- :description "Open the file in a buffer. This doesn't interfere with the user."
- :args (list '(:name "file"
-	:type string
-	:description "Path to file.."))
- :category "filesystem"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (file)
+			   (with-current-buffer (get-buffer-create file)
+			     (insert-file-contents file)
+			     (current-buffer)))
+	      :name "open-file-inactive"
+	      :description "Open the file in a background buffer. This doesn't interfere with the user."
+	      :args (list '(:name "file"
+			    :type string
+			    :description "Path to file.."))
+	      :category "filesystem"))
 #+end_src
+
 **** read-file-contents
 This reads file contents,
 #+begin_src elisp
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (filename)
                           (with-temp-buffer
@@ -243,13 +253,13 @@ This reads file contents,
 #+end_src
 **** describe-variable
 #+begin_src elisp
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (var)
-                         (let ((symbol (intern var)))
-                           (if (boundp symbol)
-                               (prin1-to-string (symbol-value symbol))
-                             (format "Variable %s is not bound." var))))
+                          (let ((symbol (intern var)))
+                            (if (boundp symbol)
+				(prin1-to-string (symbol-value symbol))
+                              (format "Variable %s is not bound." var))))
               :name "describe-variable"
               :description "See variable contents"
               :args (list '(:name "var"
@@ -260,13 +270,13 @@ This reads file contents,
 
 **** describe-function
 #+begin_src elisp
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (fun)
-                         (let ((symbol (intern fun)))
-                           (if (fboundp symbol)
-                               (prin1-to-string (documentation symbol 'function))
-                             (format "Function %s is not defined." fun))))
+                          (let ((symbol (intern fun)))
+                            (if (fboundp symbol)
+				(prin1-to-string (documentation symbol 'function))
+                              (format "Function %s is not defined." fun))))
               :name "describe-function"
               :description "See function description"
               :args (list '(:name "fun"
@@ -307,7 +317,6 @@ Pretty simple, does what it says on the tin. It gets all the tags from the =buff
 This is not, by any means, sufficient, but I do tag people and specific events frequently enough that it helps save on the context window.
 #+begin_src elisp
 (defun gptel-org-tools--org-extract-tags (buffer)
-  (interactive "bBuffer: ")
   (with-current-buffer buffer
     (let ((tags '()))
       (org-map-entries
@@ -319,15 +328,15 @@ This is not, by any means, sufficient, but I do tag people and specific events f
                (push tag tags))))))
       (sort (-uniq tags) #'string<))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-extract-tags
- :name "org-extract-tags"
- :description "Extract all unique tags from an org-mode buffer"
- :args (list '(:name "buffer"
-               :type string
-               :description "The Org buffer to extract tags from."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-extract-tags
+	      :name "org-extract-tags"
+	      :description "Extract all unique tags from an org-mode buffer"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The Org buffer to extract tags from."))
+	      :category "org-mode"))
 #+end_src
 **** org-extract-headings
 But what if there's no tags related to the topic?
@@ -337,7 +346,6 @@ Then we need to pull /some/ information from the buffer, without dragging the en
 Therefore, headings. A reasonable amount of information, and still keeping the signal-to-noise ratio pretty decent.
 #+begin_src elisp
 (defun gptel-org-tools--org-extract-headings (buffer)
-  (interactive "bBuffer: ")
   (with-current-buffer buffer
     (org-map-entries
      #'(buffer-substring-no-properties
@@ -346,15 +354,15 @@ Therefore, headings. A reasonable amount of information, and still keeping the s
      t
      'file)))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-extract-headings
- :name "org-extract-headings"
- :description "Extract all headings from an org-mode buffer"
- :args (list '(:name "buffer"
-               :type string
-               :description "The Org buffer to extract headings from."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-extract-headings
+	      :name "org-extract-headings"
+	      :description "Extract all headings from an org-mode buffer"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The Org buffer to extract headings from."))
+	      :category "org-mode"))
 #+end_src
 
 **** org-ql-select
@@ -366,6 +374,11 @@ My current goal is to replace this monstrosity with individual functions for eac
 
 But in the interim, this works. Kind of.
 
+#+begin_comment
+Currently *not* tangled, as I'm testing breaking out each type of query into its own individual tool.
+#+end_comment
+
+
 #+begin_src elisp
 (defun gptel-org-tools--org-ql-select (buf query)
   (org-ql-select
@@ -378,21 +391,21 @@ But in the interim, this works. Kind of.
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select
- :name "org-ql-select"
- :description "Run org-ql-select against buffer with query. Using filename fails."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
-             '(:name "query"
-               :type string
-               :description "The query to pass into org-ql-select. See org-ql documentation for syntax. Usually `(tags \"tag1\" \"tag2\")` is sufficient. Possible predicates: `tags` (finds both local and inherited tags), `tags-local` (finds only local tags), `rifle` (matches against both heading and body text). This is a sexp, not a string."))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select
+	      :name "org-ql-select"
+	      :description "Run org-ql-select against buffer with query. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
+			  '(:name "query"
+			    :type string
+			    :description "The query to pass into org-ql-select. See org-ql documentation for syntax. Usually `(tags \"tag1\" \"tag2\")` is sufficient. Possible predicates: `tags` (finds both local and inherited tags), `tags-local` (finds only local tags), `rifle` (matches against both heading and body text). This is a sexp, not a string."))
+	      :category "org"))
 #+end_src
 **** Somewhat working tools
 ***** org-ql-select-dates
@@ -417,7 +430,6 @@ But, any customizations to tweak this is left to the user, as everyone has their
 
 #+begin_src elisp
 (defun gptel-org-tools--org-ql-select-dates (buf date)
-  (interactive "fBuffer: \nsDate (YYYY or YYYY-MM): ")
   (org-ql-select
     (get-buffer buf)
     `(heading ,date)
@@ -427,38 +439,38 @@ But, any customizations to tweak this is left to the user, as everyone has their
 		 (org-end-of-subtree)))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-dates
- :name "org-ql-select-dates"
- :description "Extract org subtree by date in YYYY or YYYY-MM format"
- :args (list '(:name "buffer"
-               :type string
-               :description "Buffer name.")
-             '(:name "date"
-               :type string
-               :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-dates
+	      :name "org-ql-select-dates"
+	      :description "Extract org subtree by date in YYYY or YYYY-MM format"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "Buffer name.")
+			  '(:name "date"
+			    :type string
+			    :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
+	      :category "org"))
 #+end_src
 ***** org-agenda-fortnight
 This is still work in progress, the idea is to have the LLM check my calendar and see what my plans are. I have not had time to really dig into this yet.
 
 It works, in principle, but I haven't been able to find a use for it yet. The real challenge is in building a context where the tools integrate with each-other in a way that makes sense. For now, this exists.
 #+begin_src elisp
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (days)
-             (with-temp-buffer
-	       (org-agenda-list (or days 14))
-	       (let ((content (buffer-string)))
-	  (kill-buffer (current-buffer))
-	  content)))
- :name "org-agenda-fortnight"
- :description "Get the next 14 days of user's org-agenda."
- :args (list '(:name "days"
-	       :type integer
-	       :description "The number of days to look ahead. Default: 14"))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (days)
+			  (with-temp-buffer
+			    (org-agenda-list (or days 14))
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "org-agenda-fortnight"
+	      :description "Get the next 14 days of user's org-agenda."
+	      :args (list '(:name "days"
+			    :type integer
+			    :description "The number of days to look ahead. Default: 14"))
+	      :category "org"))
 #+end_src
 **** Completely WIP tools
 The following tools are still very much WIP, and I think they're self-explanatory enough. They have /NOT/ been tested in any way, shape, form, or capacity.
@@ -479,46 +491,46 @@ Retrieve the headings where the heading matches query..
 		  (line-end-position))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-headings
- :name "org-ql-select-headings"
- :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
-             '(:name "query"
-               :type string
-               :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-headings
+	      :name "org-ql-select-headings"
+	      :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
+			  '(:name "query"
+			    :type string
+			    :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
+	      :category "org-ql"))
 #+end_src
 
 ***** org-ql-select-headings-rifle
 Retrieve all the headings where either heading or content matches query.
 #+begin_src elisp
 (defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
-	     (org-ql-select
-	       (get-buffer buf)
-	       `(rifle ,query)
-	       :action :action #'(lambda ()
-		    (concat
-		      (buffer-substring-no-properties
-			(line-beginning-position)
-			(line-end-position))))))
+  (org-ql-select
+    (get-buffer buf)
+    `(rifle ,query)
+    :action #'(lambda ()
+			(concat
+			 (buffer-substring-no-properties
+			  (line-beginning-position)
+			  (line-end-position))))))
 
 
- (add-to-list 'gptel-tools
-(gptel-make-tool
-  :function #'gptel-org-tools--org-ql-select-headings-rifle
-  :name "org-ql-select-headings-rifle"
-  :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
-  :args (list '(:name "buffer"
-                :type string
-                :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
-              '(:name "query"
-                :type string
-                :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
-  :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-headings-rifle
+	      :name "org-ql-select-headings-rifle"
+	      :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
+			  '(:name "query"
+			    :type string
+			    :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
+	      :category "org-ql"))
 #+end_src
 
 ***** org-ql-select-tags-local
@@ -533,22 +545,22 @@ This pulls all the headings (and their contents) when they match tags (without i
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-tags-local
- :name "org-ql-select-tags-local"
- :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-tags-local
+	      :name "org-ql-select-tags-local"
+	      :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 #+end_src
 
 ***** org-ql-select-tags
@@ -563,51 +575,54 @@ This pulls all the headings (and their contents) when they match tags (with inhe
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-tags
- :name "org-ql-select-tags"
- :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-tags
+	      :name "org-ql-select-tags"
+	      :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 #+end_src
 
 ***** org-ql-select-rifle
 And, the "grab everything that matches" tool.
 #+begin_src elisp
 (defun gptel-org-tools--org-ql-select-rifle (buf query)
-  (org-ql-select
-    (get-buffer buf)
-    `(rifle ,query)
-    :action #'(lambda ()
-		(concat
-		 (buffer-substring-no-properties
-		  (line-beginning-position)
-		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+  (let ((buffer (get-buffer buf)))
+    (if buffer
+        (org-ql-select
+          buffer
+          `(rifle ,query)
+          :action #'(lambda ()
+                       (concat
+                        (buffer-substring-no-properties
+                         (line-beginning-position)
+                         (progn
+                           (outline-next-heading)
+                           (line-beginning-position))))))
+      (message "Buffer '%s' does not exist." buf))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-rifle
- :name "org-ql-select-rifle"
- :description "Run org-ql-select-rifle against buffer with query."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-rifle
+	      :name "org-ql-select-rifle"
+	      :description "Run org-ql-select-rifle against buffer with query."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 #+end_src
 
 
diff --git a/gptel-org-tools.el b/gptel-org-tools.el
index a5845ef..704a59d 100644
--- a/gptel-org-tools.el
+++ b/gptel-org-tools.el
@@ -34,61 +34,65 @@
 
 ;;; Code:)
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (arg)
-	     (with-temp-buffer
-	       (ibuffer)
-	       (let ((content (buffer-string)))
-	  (kill-buffer (current-buffer))
-	  content)))
- :name "list-buffers"
- :description "Access the list of buffers open in Emacs, including file names and full paths."
- :args (list '(:name "arg"
-	       :type string
-	       :description "Does nothing."
-	       :optional t))
- :category "emacs"))
+(defvar gptel-org-tools '())
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (dir)
-	     (with-temp-buffer
-	       (dired (or dir "~"))
-	       (let ((content (buffer-string)))
-		 (kill-buffer (current-buffer))
-		 content)))
- :name "dired"
- :description "List directory contents"
- :args (list '(:name "dir"
-               :type string
-               :description "Directory path"
-	       :optional t))
- :category "filesystem"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (arg)
+			  (with-temp-buffer
+			    (ibuffer)
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "list-buffers"
+	      :description "Access the list of buffers open in Emacs, including file names and full paths."
+	      :args (list '(:name "arg"
+			    :type string
+			    :description "Does nothing."
+			    :optional t))
+	      :category "emacs"))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (filename)
-	     (bufferp (find-buffer-visiting (expand-file-name filename))))
- :name "find-buffer-visiting"
- :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
- :args (list '(:name "filename"
-	       :type string
-	       :description "The filename to compare to open buffers."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (dir)
+			  (with-temp-buffer
+			    (dired (or dir "~"))
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "dired"
+	      :description "List directory contents"
+	      :args (list '(:name "dir"
+			    :type string
+			    :description "Directory path"
+			    :optional t))
+	      :category "filesystem"))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (file)
-      (find-file-noselect file))
- :name "find-file-noselect"
- :description "Open the file in a buffer. This doesn't interfere with the user."
- :args (list '(:name "file"
-	:type string
-	:description "Path to file.."))
- :category "filesystem"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (filename)
+			  (bufferp (find-buffer-visiting (expand-file-name filename))))
+	      :name "find-buffer-visiting"
+	      :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
+	      :args (list '(:name "filename"
+			    :type string
+			    :description "The filename to compare to open buffers."))
+	      :category "org-mode"))
 
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (file)
+			   (with-current-buffer (get-buffer-create file)
+			     (insert-file-contents file)
+			     (current-buffer)))
+	      :name "open-file-inactive"
+	      :description "Open the file in a background buffer. This doesn't interfere with the user."
+	      :args (list '(:name "file"
+			    :type string
+			    :description "Path to file.."))
+	      :category "filesystem"))
+
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (filename)
                           (with-temp-buffer
@@ -101,13 +105,13 @@
                             :description "The filename to read."))
               :category "org-mode"))
 
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (var)
-                         (let ((symbol (intern var)))
-                           (if (boundp symbol)
-                               (prin1-to-string (symbol-value symbol))
-                             (format "Variable %s is not bound." var))))
+                          (let ((symbol (intern var)))
+                            (if (boundp symbol)
+				(prin1-to-string (symbol-value symbol))
+                              (format "Variable %s is not bound." var))))
               :name "describe-variable"
               :description "See variable contents"
               :args (list '(:name "var"
@@ -115,13 +119,13 @@
                             :description "Variable name"))
               :category "emacs"))
 
-(add-to-list 'gptel-tools
+(add-to-list 'gptel-org-tools
              (gptel-make-tool
               :function (lambda (fun)
-                         (let ((symbol (intern fun)))
-                           (if (fboundp symbol)
-                               (prin1-to-string (documentation symbol 'function))
-                             (format "Function %s is not defined." fun))))
+                          (let ((symbol (intern fun)))
+                            (if (fboundp symbol)
+				(prin1-to-string (documentation symbol 'function))
+                              (format "Function %s is not defined." fun))))
               :name "describe-function"
               :description "See function description"
               :args (list '(:name "fun"
@@ -131,7 +135,6 @@
               :category "emacs"))
 
 (defun gptel-org-tools--org-extract-tags (buffer)
-  (interactive "bBuffer: ")
   (with-current-buffer buffer
     (let ((tags '()))
       (org-map-entries
@@ -143,18 +146,17 @@
                (push tag tags))))))
       (sort (-uniq tags) #'string<))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-extract-tags
- :name "org-extract-tags"
- :description "Extract all unique tags from an org-mode buffer"
- :args (list '(:name "buffer"
-               :type string
-               :description "The Org buffer to extract tags from."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-extract-tags
+	      :name "org-extract-tags"
+	      :description "Extract all unique tags from an org-mode buffer"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The Org buffer to extract tags from."))
+	      :category "org-mode"))
 
 (defun gptel-org-tools--org-extract-headings (buffer)
-  (interactive "bBuffer: ")
   (with-current-buffer buffer
     (org-map-entries
      #'(buffer-substring-no-properties
@@ -163,15 +165,15 @@
      t
      'file)))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-extract-headings
- :name "org-extract-headings"
- :description "Extract all headings from an org-mode buffer"
- :args (list '(:name "buffer"
-               :type string
-               :description "The Org buffer to extract headings from."))
- :category "org-mode"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-extract-headings
+	      :name "org-extract-headings"
+	      :description "Extract all headings from an org-mode buffer"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The Org buffer to extract headings from."))
+	      :category "org-mode"))
 
 (defun gptel-org-tools--org-ql-select (buf query)
   (org-ql-select
@@ -184,24 +186,23 @@
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select
- :name "org-ql-select"
- :description "Run org-ql-select against buffer with query. Using filename fails."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
-             '(:name "query"
-               :type string
-               :description "The query to pass into org-ql-select. See org-ql documentation for syntax. Usually `(tags \"tag1\" \"tag2\")` is sufficient. Possible predicates: `tags` (finds both local and inherited tags), `tags-local` (finds only local tags), `rifle` (matches against both heading and body text). This is a sexp, not a string."))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select
+	      :name "org-ql-select"
+	      :description "Run org-ql-select against buffer with query. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
+			  '(:name "query"
+			    :type string
+			    :description "The query to pass into org-ql-select. See org-ql documentation for syntax. Usually `(tags \"tag1\" \"tag2\")` is sufficient. Possible predicates: `tags` (finds both local and inherited tags), `tags-local` (finds only local tags), `rifle` (matches against both heading and body text). This is a sexp, not a string."))
+	      :category "org"))
 
 (defun gptel-org-tools--org-ql-select-dates (buf date)
-  (interactive "fBuffer: \nsDate (YYYY or YYYY-MM): ")
   (org-ql-select
     (get-buffer buf)
     `(heading ,date)
@@ -211,33 +212,33 @@
 		 (org-end-of-subtree)))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-dates
- :name "org-ql-select-dates"
- :description "Extract org subtree by date in YYYY or YYYY-MM format"
- :args (list '(:name "buffer"
-               :type string
-               :description "Buffer name.")
-             '(:name "date"
-               :type string
-               :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-dates
+	      :name "org-ql-select-dates"
+	      :description "Extract org subtree by date in YYYY or YYYY-MM format"
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "Buffer name.")
+			  '(:name "date"
+			    :type string
+			    :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
+	      :category "org"))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function (lambda (days)
-             (with-temp-buffer
-	       (org-agenda-list (or days 14))
-	       (let ((content (buffer-string)))
-	  (kill-buffer (current-buffer))
-	  content)))
- :name "org-agenda-fortnight"
- :description "Get the next 14 days of user's org-agenda."
- :args (list '(:name "days"
-	       :type integer
-	       :description "The number of days to look ahead. Default: 14"))
- :category "org"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function (lambda (days)
+			  (with-temp-buffer
+			    (org-agenda-list (or days 14))
+			    (let ((content (buffer-string)))
+			      (kill-buffer (current-buffer))
+			      content)))
+	      :name "org-agenda-fortnight"
+	      :description "Get the next 14 days of user's org-agenda."
+	      :args (list '(:name "days"
+			    :type integer
+			    :description "The number of days to look ahead. Default: 14"))
+	      :category "org"))
 
 (defun gptel-org-tools--org-ql-select-headings (buf query)
   (org-ql-select
@@ -250,42 +251,42 @@
 		  (line-end-position))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-headings
- :name "org-ql-select-headings"
- :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
-             '(:name "query"
-               :type string
-               :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-headings
+	      :name "org-ql-select-headings"
+	      :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
+			  '(:name "query"
+			    :type string
+			    :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
+	      :category "org-ql"))
 
 (defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
-	     (org-ql-select
-	       (get-buffer buf)
-	       `(rifle ,query)
-	       :action :action #'(lambda ()
-		    (concat
-		      (buffer-substring-no-properties
-			(line-beginning-position)
-			(line-end-position))))))
+  (org-ql-select
+    (get-buffer buf)
+    `(rifle ,query)
+    :action #'(lambda ()
+			(concat
+			 (buffer-substring-no-properties
+			  (line-beginning-position)
+			  (line-end-position))))))
 
 
- (add-to-list 'gptel-tools
-(gptel-make-tool
-  :function #'gptel-org-tools--org-ql-select-headings-rifle
-  :name "org-ql-select-headings-rifle"
-  :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
-  :args (list '(:name "buffer"
-                :type string
-                :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
-              '(:name "query"
-                :type string
-                :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
-  :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-headings-rifle
+	      :name "org-ql-select-headings-rifle"
+	      :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
+			  '(:name "query"
+			    :type string
+			    :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
+	      :category "org-ql"))
 
 (defun gptel-org-tools--org-ql-select-tags-local (buf query)
   (org-ql-select
@@ -296,22 +297,22 @@
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-tags-local
- :name "org-ql-select-tags-local"
- :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-tags-local
+	      :name "org-ql-select-tags-local"
+	      :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 
 (defun gptel-org-tools--org-ql-select-tags (buf query)
   (org-ql-select
@@ -322,47 +323,50 @@
 		 (buffer-substring-no-properties
 		  (line-beginning-position)
 		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+		    (outline-next-heading)
+		    (line-beginning-position)))))))
 
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-tags
- :name "org-ql-select-tags"
- :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-tags
+	      :name "org-ql-select-tags"
+	      :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 
 (defun gptel-org-tools--org-ql-select-rifle (buf query)
-  (org-ql-select
-    (get-buffer buf)
-    `(rifle ,query)
-    :action #'(lambda ()
-		(concat
-		 (buffer-substring-no-properties
-		  (line-beginning-position)
-		  (progn
-		  (outline-next-heading)
-		  (line-beginning-position)))))))
+  (let ((buffer (get-buffer buf)))
+    (if buffer
+        (org-ql-select
+          buffer
+          `(rifle ,query)
+          :action #'(lambda ()
+                       (concat
+                        (buffer-substring-no-properties
+                         (line-beginning-position)
+                         (progn
+                           (outline-next-heading)
+                           (line-beginning-position))))))
+      (message "Buffer '%s' does not exist." buf))))
 
-(add-to-list 'gptel-tools
-(gptel-make-tool
- :function #'gptel-org-tools--org-ql-select-rifle
- :name "org-ql-select-rifle"
- :description "Run org-ql-select-rifle against buffer with query."
- :args (list '(:name "buffer"
-               :type string
-               :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
-             '(:name "query"
-               :type string
-               :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
- :category "org-ql"))
+(add-to-list 'gptel-org-tools
+	     (gptel-make-tool
+	      :function #'gptel-org-tools--org-ql-select-rifle
+	      :name "org-ql-select-rifle"
+	      :description "Run org-ql-select-rifle against buffer with query."
+	      :args (list '(:name "buffer"
+			    :type string
+			    :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
+			  '(:name "query"
+			    :type string
+			    :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
+	      :category "org-ql"))
 
 (provide 'gptel-org-tools)
 ;;; gptel-org-tools.el ends here