Fix org-ql-select-rifle

This commit is contained in:
Phil Bajsicki 2025-04-15 21:17:23 +02:00
parent f7b3de3789
commit 44d56bdefd
2 changed files with 451 additions and 432 deletions

View file

@ -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-model 'llamacpp)
(setq gptel-include-reasoning t) (setq gptel-include-reasoning t)
(setq gptel-backend (gptel-make-deepseek "llamacpp" (setq gptel-backend (gptel-make-deepseek "llamacpp"
:host "localhost:8080" :host "localhost:8080"
:protocol "http" :protocol "http"
:stream nil :stream nil
:models '("llamacpp" :models '("llamacpp"
:capabilities (reasoning)) :capabilities (reasoning))
:request-params '(:thinking t :request-params '(:thinking t
:enable_thinking t :enable_thinking t
:include_reasoning t :include_reasoning t
:parallel_tool_calls t))) :parallel_tool_calls t)))
(setf (alist-get 'org-mode gptel-prompt-prefix-alist) "@user\n") (setf (alist-get 'org-mode gptel-prompt-prefix-alist) "@user\n")
(setf (alist-get 'org-mode gptel-response-prefix-alist) "@assistant\n") (setf (alist-get 'org-mode gptel-response-prefix-alist) "@assistant\n")
@ -120,6 +120,12 @@ Stuff, headers, etc.
;;; Code:) ;;; Code:)
#+end_src #+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 ** The tools
*** Emacs *** Emacs
These tools are primarily concerned with Emacs, Emacs Lisp, and files-or-buffers. 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. Highly not recommended, but sometimes an LLM can pull a rabbit out of pure entropy.
#+begin_src elisp :tangle no #+begin_src elisp :tangle no
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (elisp) :function (lambda (elisp)
(unless (stringp elisp) (error "elisp code must be a string")) (unless (stringp elisp) (error "elisp code must be a string"))
(with-temp-buffer (with-temp-buffer
(insert (eval (read elisp))) (insert (eval (read elisp)))
(buffer-string))) (buffer-string)))
:name "eval" :name "eval"
:description "Execute arbitrary Emacs Lisp code" :description "Execute arbitrary Emacs Lisp code"
:args (list '(:name "eval" :args (list '(:name "eval"
:type string :type string
:description "The Emacs Lisp code to evaluate.")) :description "The Emacs Lisp code to evaluate."))
:category "emacs")) :category "emacs"
:confirm t))
#+end_src #+end_src
**** list-buffers **** 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 Seems to be one of the most reliable tools in the basket... mostly because
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (arg) :function (lambda (arg)
(with-temp-buffer (with-temp-buffer
(ibuffer) (ibuffer)
(let ((content (buffer-string))) (let ((content (buffer-string)))
(kill-buffer (current-buffer)) (kill-buffer (current-buffer))
content))) content)))
:name "list-buffers" :name "list-buffers"
:description "Access the list of buffers open in Emacs, including file names and full paths." :description "Access the list of buffers open in Emacs, including file names and full paths."
:args (list '(:name "arg" :args (list '(:name "arg"
:type string :type string
:description "Does nothing." :description "Does nothing."
:optional t)) :optional t))
:category "emacs")) :category "emacs"))
#+end_src #+end_src
**** dired **** 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. 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 #+end_comment
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (dir) :function (lambda (dir)
(with-temp-buffer (with-temp-buffer
(dired (or dir "~")) (dired (or dir "~"))
(let ((content (buffer-string))) (let ((content (buffer-string)))
(kill-buffer (current-buffer)) (kill-buffer (current-buffer))
content))) content)))
:name "dired" :name "dired"
:description "List directory contents" :description "List directory contents"
:args (list '(:name "dir" :args (list '(:name "dir"
:type string :type string
:description "Directory path" :description "Directory path"
:optional t)) :optional t))
:category "filesystem")) :category "filesystem"))
#+end_src #+end_src
**** find-buffer-visiting **** find-buffer-visiting
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (filename) :function (lambda (filename)
(bufferp (find-buffer-visiting (expand-file-name filename)))) (bufferp (find-buffer-visiting (expand-file-name filename))))
:name "find-buffer-visiting" :name "find-buffer-visiting"
:description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)" :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
:args (list '(:name "filename" :args (list '(:name "filename"
:type string :type string
:description "The filename to compare to open buffers.")) :description "The filename to compare to open buffers."))
:category "org-mode")) :category "org-mode"))
#+end_src #+end_src
**** find-file-noselect **** open-file-inactive
Continuation from above. Open a file into a buffer for processing. Onec it's found by dired-list. Continuation from above. Open a file into a buffer for processing, once it's found by dired-list.
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (file) :function (lambda (file)
(find-file-noselect file)) (with-current-buffer (get-buffer-create file)
:name "find-file-noselect" (insert-file-contents file)
:description "Open the file in a buffer. This doesn't interfere with the user." (current-buffer)))
:args (list '(:name "file" :name "open-file-inactive"
:type string :description "Open the file in a background buffer. This doesn't interfere with the user."
:description "Path to file..")) :args (list '(:name "file"
:category "filesystem")) :type string
:description "Path to file.."))
:category "filesystem"))
#+end_src #+end_src
**** read-file-contents **** read-file-contents
This reads file contents, This reads file contents,
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (filename) :function (lambda (filename)
(with-temp-buffer (with-temp-buffer
@ -243,13 +253,13 @@ This reads file contents,
#+end_src #+end_src
**** describe-variable **** describe-variable
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (var) :function (lambda (var)
(let ((symbol (intern var))) (let ((symbol (intern var)))
(if (boundp symbol) (if (boundp symbol)
(prin1-to-string (symbol-value symbol)) (prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound." var)))) (format "Variable %s is not bound." var))))
:name "describe-variable" :name "describe-variable"
:description "See variable contents" :description "See variable contents"
:args (list '(:name "var" :args (list '(:name "var"
@ -260,13 +270,13 @@ This reads file contents,
**** describe-function **** describe-function
#+begin_src elisp #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (fun) :function (lambda (fun)
(let ((symbol (intern fun))) (let ((symbol (intern fun)))
(if (fboundp symbol) (if (fboundp symbol)
(prin1-to-string (documentation symbol 'function)) (prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun)))) (format "Function %s is not defined." fun))))
:name "describe-function" :name "describe-function"
:description "See function description" :description "See function description"
:args (list '(:name "fun" :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. 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 #+begin_src elisp
(defun gptel-org-tools--org-extract-tags (buffer) (defun gptel-org-tools--org-extract-tags (buffer)
(interactive "bBuffer: ")
(with-current-buffer buffer (with-current-buffer buffer
(let ((tags '())) (let ((tags '()))
(org-map-entries (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)))))) (push tag tags))))))
(sort (-uniq tags) #'string<)))) (sort (-uniq tags) #'string<))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-extract-tags :function #'gptel-org-tools--org-extract-tags
:name "org-extract-tags" :name "org-extract-tags"
:description "Extract all unique tags from an org-mode buffer" :description "Extract all unique tags from an org-mode buffer"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The Org buffer to extract tags from.")) :description "The Org buffer to extract tags from."))
:category "org-mode")) :category "org-mode"))
#+end_src #+end_src
**** org-extract-headings **** org-extract-headings
But what if there's no tags related to the topic? 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. Therefore, headings. A reasonable amount of information, and still keeping the signal-to-noise ratio pretty decent.
#+begin_src elisp #+begin_src elisp
(defun gptel-org-tools--org-extract-headings (buffer) (defun gptel-org-tools--org-extract-headings (buffer)
(interactive "bBuffer: ")
(with-current-buffer buffer (with-current-buffer buffer
(org-map-entries (org-map-entries
#'(buffer-substring-no-properties #'(buffer-substring-no-properties
@ -346,15 +354,15 @@ Therefore, headings. A reasonable amount of information, and still keeping the s
t t
'file))) 'file)))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-extract-headings :function #'gptel-org-tools--org-extract-headings
:name "org-extract-headings" :name "org-extract-headings"
:description "Extract all headings from an org-mode buffer" :description "Extract all headings from an org-mode buffer"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The Org buffer to extract headings from.")) :description "The Org buffer to extract headings from."))
:category "org-mode")) :category "org-mode"))
#+end_src #+end_src
**** org-ql-select **** 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. 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 #+begin_src elisp
(defun gptel-org-tools--org-ql-select (buf query) (defun gptel-org-tools--org-ql-select (buf query)
(org-ql-select (org-ql-select
@ -378,21 +391,21 @@ But in the interim, this works. Kind of.
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select :function #'gptel-org-tools--org-ql-select
:name "org-ql-select" :name "org-ql-select"
:description "Run org-ql-select against buffer with query. Using filename fails." :description "Run org-ql-select against buffer with query. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.") :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
'(:name "query" '(:name "query"
:type string :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.")) :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")) :category "org"))
#+end_src #+end_src
**** Somewhat working tools **** Somewhat working tools
***** org-ql-select-dates ***** 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 #+begin_src elisp
(defun gptel-org-tools--org-ql-select-dates (buf date) (defun gptel-org-tools--org-ql-select-dates (buf date)
(interactive "fBuffer: \nsDate (YYYY or YYYY-MM): ")
(org-ql-select (org-ql-select
(get-buffer buf) (get-buffer buf)
`(heading ,date) `(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))))) (org-end-of-subtree)))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-dates :function #'gptel-org-tools--org-ql-select-dates
:name "org-ql-select-dates" :name "org-ql-select-dates"
:description "Extract org subtree by date in YYYY or YYYY-MM format" :description "Extract org subtree by date in YYYY or YYYY-MM format"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "Buffer name.") :description "Buffer name.")
'(:name "date" '(:name "date"
:type string :type string
:description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\"")) :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
:category "org")) :category "org"))
#+end_src #+end_src
***** org-agenda-fortnight ***** 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. 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. 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 #+begin_src elisp
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (days) :function (lambda (days)
(with-temp-buffer (with-temp-buffer
(org-agenda-list (or days 14)) (org-agenda-list (or days 14))
(let ((content (buffer-string))) (let ((content (buffer-string)))
(kill-buffer (current-buffer)) (kill-buffer (current-buffer))
content))) content)))
:name "org-agenda-fortnight" :name "org-agenda-fortnight"
:description "Get the next 14 days of user's org-agenda." :description "Get the next 14 days of user's org-agenda."
:args (list '(:name "days" :args (list '(:name "days"
:type integer :type integer
:description "The number of days to look ahead. Default: 14")) :description "The number of days to look ahead. Default: 14"))
:category "org")) :category "org"))
#+end_src #+end_src
**** Completely WIP tools **** 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. 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)))))) (line-end-position))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings :function #'gptel-org-tools--org-ql-select-headings
:name "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." :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.") :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
'(:name "query" '(:name "query"
:type string :type string
:description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\"")) :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
:category "org-ql")) :category "org-ql"))
#+end_src #+end_src
***** org-ql-select-headings-rifle ***** org-ql-select-headings-rifle
Retrieve all the headings where either heading or content matches query. Retrieve all the headings where either heading or content matches query.
#+begin_src elisp #+begin_src elisp
(defun gptel-org-tools--org-ql-select-headings-rifle (buf query) (defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
(org-ql-select (org-ql-select
(get-buffer buf) (get-buffer buf)
`(rifle ,query) `(rifle ,query)
:action :action #'(lambda () :action #'(lambda ()
(concat (concat
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(line-end-position)))))) (line-end-position))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle :function #'gptel-org-tools--org-ql-select-headings-rifle
:name "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." :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.") :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
'(:name "query" '(:name "query"
:type string :type string
:description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\"")) :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
:category "org-ql")) :category "org-ql"))
#+end_src #+end_src
***** org-ql-select-tags-local ***** 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 (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local :function #'gptel-org-tools--org-ql-select-tags-local
:name "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." :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The tags to match entry headings against. Example: \"tag1\" \"tag2\"")) :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
#+end_src #+end_src
***** org-ql-select-tags ***** 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 (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags :function #'gptel-org-tools--org-ql-select-tags
:name "org-ql-select-tags" :name "org-ql-select-tags"
:description "Run org-ql-select-tags against buffer with query. Supports tag inheritance." :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The tags to match entry headings against. Example: \"tag1\" \"tag2\"")) :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
#+end_src #+end_src
***** org-ql-select-rifle ***** org-ql-select-rifle
And, the "grab everything that matches" tool. And, the "grab everything that matches" tool.
#+begin_src elisp #+begin_src elisp
(defun gptel-org-tools--org-ql-select-rifle (buf query) (defun gptel-org-tools--org-ql-select-rifle (buf query)
(org-ql-select (let ((buffer (get-buffer buf)))
(get-buffer buf) (if buffer
`(rifle ,query) (org-ql-select
:action #'(lambda () buffer
(concat `(rifle ,query)
(buffer-substring-no-properties :action #'(lambda ()
(line-beginning-position) (concat
(progn (buffer-substring-no-properties
(outline-next-heading) (line-beginning-position)
(line-beginning-position))))))) (progn
(outline-next-heading)
(line-beginning-position))))))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle :function #'gptel-org-tools--org-ql-select-rifle
:name "org-ql-select-rifle" :name "org-ql-select-rifle"
:description "Run org-ql-select-rifle against buffer with query." :description "Run org-ql-select-rifle against buffer with query."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\"")) :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
#+end_src #+end_src

View file

@ -34,61 +34,65 @@
;;; Code:) ;;; Code:)
(add-to-list 'gptel-tools (defvar 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 (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (dir) :function (lambda (arg)
(with-temp-buffer (with-temp-buffer
(dired (or dir "~")) (ibuffer)
(let ((content (buffer-string))) (let ((content (buffer-string)))
(kill-buffer (current-buffer)) (kill-buffer (current-buffer))
content))) content)))
:name "dired" :name "list-buffers"
:description "List directory contents" :description "Access the list of buffers open in Emacs, including file names and full paths."
:args (list '(:name "dir" :args (list '(:name "arg"
:type string :type string
:description "Directory path" :description "Does nothing."
:optional t)) :optional t))
:category "filesystem")) :category "emacs"))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (filename) :function (lambda (dir)
(bufferp (find-buffer-visiting (expand-file-name filename)))) (with-temp-buffer
:name "find-buffer-visiting" (dired (or dir "~"))
:description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)" (let ((content (buffer-string)))
:args (list '(:name "filename" (kill-buffer (current-buffer))
:type string content)))
:description "The filename to compare to open buffers.")) :name "dired"
:category "org-mode")) :description "List directory contents"
:args (list '(:name "dir"
:type string
:description "Directory path"
:optional t))
:category "filesystem"))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (file) :function (lambda (filename)
(find-file-noselect file)) (bufferp (find-buffer-visiting (expand-file-name filename))))
:name "find-file-noselect" :name "find-buffer-visiting"
:description "Open the file in a buffer. This doesn't interfere with the user." :description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
:args (list '(:name "file" :args (list '(:name "filename"
:type string :type string
:description "Path to file..")) :description "The filename to compare to open buffers."))
:category "filesystem")) :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 (gptel-make-tool
:function (lambda (filename) :function (lambda (filename)
(with-temp-buffer (with-temp-buffer
@ -101,13 +105,13 @@
:description "The filename to read.")) :description "The filename to read."))
:category "org-mode")) :category "org-mode"))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (var) :function (lambda (var)
(let ((symbol (intern var))) (let ((symbol (intern var)))
(if (boundp symbol) (if (boundp symbol)
(prin1-to-string (symbol-value symbol)) (prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound." var)))) (format "Variable %s is not bound." var))))
:name "describe-variable" :name "describe-variable"
:description "See variable contents" :description "See variable contents"
:args (list '(:name "var" :args (list '(:name "var"
@ -115,13 +119,13 @@
:description "Variable name")) :description "Variable name"))
:category "emacs")) :category "emacs"))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (fun) :function (lambda (fun)
(let ((symbol (intern fun))) (let ((symbol (intern fun)))
(if (fboundp symbol) (if (fboundp symbol)
(prin1-to-string (documentation symbol 'function)) (prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun)))) (format "Function %s is not defined." fun))))
:name "describe-function" :name "describe-function"
:description "See function description" :description "See function description"
:args (list '(:name "fun" :args (list '(:name "fun"
@ -131,7 +135,6 @@
:category "emacs")) :category "emacs"))
(defun gptel-org-tools--org-extract-tags (buffer) (defun gptel-org-tools--org-extract-tags (buffer)
(interactive "bBuffer: ")
(with-current-buffer buffer (with-current-buffer buffer
(let ((tags '())) (let ((tags '()))
(org-map-entries (org-map-entries
@ -143,18 +146,17 @@
(push tag tags)))))) (push tag tags))))))
(sort (-uniq tags) #'string<)))) (sort (-uniq tags) #'string<))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-extract-tags :function #'gptel-org-tools--org-extract-tags
:name "org-extract-tags" :name "org-extract-tags"
:description "Extract all unique tags from an org-mode buffer" :description "Extract all unique tags from an org-mode buffer"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The Org buffer to extract tags from.")) :description "The Org buffer to extract tags from."))
:category "org-mode")) :category "org-mode"))
(defun gptel-org-tools--org-extract-headings (buffer) (defun gptel-org-tools--org-extract-headings (buffer)
(interactive "bBuffer: ")
(with-current-buffer buffer (with-current-buffer buffer
(org-map-entries (org-map-entries
#'(buffer-substring-no-properties #'(buffer-substring-no-properties
@ -163,15 +165,15 @@
t t
'file))) 'file)))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-extract-headings :function #'gptel-org-tools--org-extract-headings
:name "org-extract-headings" :name "org-extract-headings"
:description "Extract all headings from an org-mode buffer" :description "Extract all headings from an org-mode buffer"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The Org buffer to extract headings from.")) :description "The Org buffer to extract headings from."))
:category "org-mode")) :category "org-mode"))
(defun gptel-org-tools--org-ql-select (buf query) (defun gptel-org-tools--org-ql-select (buf query)
(org-ql-select (org-ql-select
@ -184,24 +186,23 @@
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select :function #'gptel-org-tools--org-ql-select
:name "org-ql-select" :name "org-ql-select"
:description "Run org-ql-select against buffer with query. Using filename fails." :description "Run org-ql-select against buffer with query. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.") :description "The name of the buffer. Can be multiple buffers. See the NAME column in `emacs-list-buffers`.")
'(:name "query" '(:name "query"
:type string :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.")) :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")) :category "org"))
(defun gptel-org-tools--org-ql-select-dates (buf date) (defun gptel-org-tools--org-ql-select-dates (buf date)
(interactive "fBuffer: \nsDate (YYYY or YYYY-MM): ")
(org-ql-select (org-ql-select
(get-buffer buf) (get-buffer buf)
`(heading ,date) `(heading ,date)
@ -211,33 +212,33 @@
(org-end-of-subtree))))) (org-end-of-subtree)))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-dates :function #'gptel-org-tools--org-ql-select-dates
:name "org-ql-select-dates" :name "org-ql-select-dates"
:description "Extract org subtree by date in YYYY or YYYY-MM format" :description "Extract org subtree by date in YYYY or YYYY-MM format"
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "Buffer name.") :description "Buffer name.")
'(:name "date" '(:name "date"
:type string :type string
:description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\"")) :description "Date in YYYY or YYYY-MM format. Can add multiple like so: \"YYYY-MM\" \"YYYY-MM\""))
:category "org")) :category "org"))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function (lambda (days) :function (lambda (days)
(with-temp-buffer (with-temp-buffer
(org-agenda-list (or days 14)) (org-agenda-list (or days 14))
(let ((content (buffer-string))) (let ((content (buffer-string)))
(kill-buffer (current-buffer)) (kill-buffer (current-buffer))
content))) content)))
:name "org-agenda-fortnight" :name "org-agenda-fortnight"
:description "Get the next 14 days of user's org-agenda." :description "Get the next 14 days of user's org-agenda."
:args (list '(:name "days" :args (list '(:name "days"
:type integer :type integer
:description "The number of days to look ahead. Default: 14")) :description "The number of days to look ahead. Default: 14"))
:category "org")) :category "org"))
(defun gptel-org-tools--org-ql-select-headings (buf query) (defun gptel-org-tools--org-ql-select-headings (buf query)
(org-ql-select (org-ql-select
@ -250,42 +251,42 @@
(line-end-position)))))) (line-end-position))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings :function #'gptel-org-tools--org-ql-select-headings
:name "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." :description "Retreive matching headings from buffer using org-ql-select. Matches only against heading. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.") :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
'(:name "query" '(:name "query"
:type string :type string
:description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\"")) :description "The string to pass into org-ql-select-headings. This is a bare string. Example: \"searchterm\""))
:category "org-ql")) :category "org-ql"))
(defun gptel-org-tools--org-ql-select-headings-rifle (buf query) (defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
(org-ql-select (org-ql-select
(get-buffer buf) (get-buffer buf)
`(rifle ,query) `(rifle ,query)
:action :action #'(lambda () :action #'(lambda ()
(concat (concat
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(line-end-position)))))) (line-end-position))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle :function #'gptel-org-tools--org-ql-select-headings-rifle
:name "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." :description "Retreive headings from buffer using org-ql-select. Matches against both heading and content. Using filename fails."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.") :description "The name of the buffer. See the NAME column in ~emacs-list-buffers~.")
'(:name "query" '(:name "query"
:type string :type string
:description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\"")) :description "The string to pass into org-ql-select-headings-rifle. This is a bare string. Example: \"searchterm\""))
:category "org-ql")) :category "org-ql"))
(defun gptel-org-tools--org-ql-select-tags-local (buf query) (defun gptel-org-tools--org-ql-select-tags-local (buf query)
(org-ql-select (org-ql-select
@ -296,22 +297,22 @@
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local :function #'gptel-org-tools--org-ql-select-tags-local
:name "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." :description "Run org-ql-select-tags-local against buffer with query. No tag inheritance."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The tags to match entry headings against. Example: \"tag1\" \"tag2\"")) :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
(defun gptel-org-tools--org-ql-select-tags (buf query) (defun gptel-org-tools--org-ql-select-tags (buf query)
(org-ql-select (org-ql-select
@ -322,47 +323,50 @@
(buffer-substring-no-properties (buffer-substring-no-properties
(line-beginning-position) (line-beginning-position)
(progn (progn
(outline-next-heading) (outline-next-heading)
(line-beginning-position))))))) (line-beginning-position)))))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags :function #'gptel-org-tools--org-ql-select-tags
:name "org-ql-select-tags" :name "org-ql-select-tags"
:description "Run org-ql-select-tags against buffer with query. Supports tag inheritance." :description "Run org-ql-select-tags against buffer with query. Supports tag inheritance."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The tags to match entry headings against. Example: \"tag1\" \"tag2\"")) :description "The tags to match entry headings against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
(defun gptel-org-tools--org-ql-select-rifle (buf query) (defun gptel-org-tools--org-ql-select-rifle (buf query)
(org-ql-select (let ((buffer (get-buffer buf)))
(get-buffer buf) (if buffer
`(rifle ,query) (org-ql-select
:action #'(lambda () buffer
(concat `(rifle ,query)
(buffer-substring-no-properties :action #'(lambda ()
(line-beginning-position) (concat
(progn (buffer-substring-no-properties
(outline-next-heading) (line-beginning-position)
(line-beginning-position))))))) (progn
(outline-next-heading)
(line-beginning-position))))))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-tools (add-to-list 'gptel-org-tools
(gptel-make-tool (gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle :function #'gptel-org-tools--org-ql-select-rifle
:name "org-ql-select-rifle" :name "org-ql-select-rifle"
:description "Run org-ql-select-rifle against buffer with query." :description "Run org-ql-select-rifle against buffer with query."
:args (list '(:name "buffer" :args (list '(:name "buffer"
:type string :type string
:description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.") :description "The name of the buffer. See the NAME column in `emacs-list-buffers`. Using filename fails.")
'(:name "query" '(:name "query"
:type string :type string
:description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\"")) :description "The strings to match entry headings and content against. Example: \"tag1\" \"tag2\""))
:category "org-ql")) :category "org-ql"))
(provide 'gptel-org-tools) (provide 'gptel-org-tools)
;;; gptel-org-tools.el ends here ;;; gptel-org-tools.el ends here