Bump version. Bunch of updates. Semi-stable?

This commit is contained in:
Phil Bajsicki 2025-04-23 22:31:41 +02:00
parent 18c52fa9d7
commit d6332967c8
2 changed files with 319 additions and 221 deletions

View file

@ -38,8 +38,7 @@ These are primarily for my own use. Please don't expect quality.
This forge is for my personal use, and as such: [[https://bajsicki.com/contact/][contact]], just in case there's questions, issues, whatnot.
* My system
I'm running LLMs exclusively locally, on the smaller side.
With Qwen 14B, I'm getting reasonably good results with the following set-up:
I'm running LLMs exclusively locally, on the smaller side. I'm getting reasonably good results with the following set-up:
- Hardware: RX 7900XTX
- Software:
- [[https://github.com/ggml-org/llama.cpp][llama.cpp]] compiled with ROCm 6.4.
@ -117,7 +116,7 @@ Stuff, headers, etc.
;; Keywords: extensions, comm, tools, matching, convenience,
;;
;; Author: Phil Bajsicki <phil@bajsicki.com>
;; Version: 0.0.1
;; Version: 0.0.2
;; Package-Requires: ((emacs "30.1") (gptel 0.9.8) (org-ql 0.9))
;; URL: https://github.com/phil/gptel-org-tools
;; SPDX-License-Identifier: GPL-3.0
@ -181,23 +180,23 @@ These abstract away some of the tool definitions.
*** Retrieve heading and body (without subheadings)
#+begin_src elisp
(defun gptel-org-tools--heading-body ()
(concat
(buffer-substring-no-properties
(line-beginning-position)
(progn
(outline-next-heading)
(line-beginning-position)))
"---\n"))
(concat
(buffer-substring-no-properties
(line-beginning-position)
(progn
(outline-next-heading)
(line-beginning-position)))
"---\n"))
#+end_src
*** Retrieve heading and subheadings (until next same-level heading)
#+begin_src elisp
(defun gptel-org-tools--heading-subtree ()
(concat
(buffer-substring-no-properties
(line-beginning-position)
(org-end-of-subtree))
"---\n"))
(concat
(buffer-substring-no-properties
(line-beginning-position)
(org-end-of-subtree))
"---\n"))
#+end_src
** Note on org-ql (caching)
There isn't (yet?) a good way to disable caching. This means that repeated queries will return the same output, until org-ql-cache is cleared.
@ -215,7 +214,6 @@ So for now, you can manually re-set the cache like so:
** The tools
*** Emacs
These tools are primarily concerned with Emacs, Emacs Lisp, and files-or-buffers.
**** eval
Dangerous, but occasionally useful for pure chaos and amusement...
I would like to say. But in actuality, especially with the 'smarter' models, they can surprise with the varied approaches they have to problem-solving.
@ -225,15 +223,17 @@ 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
(defun gptel-org-tools--eval (elisp)
(unless (stringp elisp) (error "elisp code must be a string"))
(with-temp-buffer
(insert (eval (read elisp)))
(buffer-string)))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function ((lambda () (interactive) ) (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"
:function #'gptel-org-tools--eval
:name "gptel-org-tools--eval"
:description "Execute Emacs Lisp code"
:args (list '(:name "eval"
:type string
:description "The Emacs Lisp code to evaluate."))
@ -248,13 +248,15 @@ The rationale behind using ~list-buffers~ is the same as with dired. They both d
Seems to be one of the most reliable tools in the basket... mostly because
#+begin_src elisp
(defun gptel-org-tool--list-buffers ()
(list-buffers-noselect)
(with-temp-buffer "*Buffer List*"
(buffer-string)))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (arg)
(list-buffers-noselect)
(with-current-buffer "*Buffer List*"
(buffer-string)))
:name "list-buffers"
:function #'gptel-org-tool--list-buffers
:name "gptel-org-tool--list-buffers"
:description "Access the list of buffers open in Emacs, including file names and full paths."
:args (list '(:name "arg"
:type string
@ -273,15 +275,17 @@ Be sure to customize the function to point to your org directory, if you wish. I
#+end_comment
#+begin_src elisp
(defun gptel-org-tools--dir (dir)
(with-temp-buffer
(dired (or dir "~"))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
content)))
(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"
:function #'gptel-org-tools--dir
:name "gptel-org-tools--dir"
:description "List directory contents"
:args (list '(:name "dir"
:type string
@ -292,14 +296,17 @@ Be sure to customize the function to point to your org directory, if you wish. I
**** find-buffer-visiting
Disabled for now, as it's causing some issues.
#+begin_src elisp
#+begin_src elisp :tangle no :results none
(defun gptel-org-tools--find-buffer-visiting (filename)
(concat
(bufferp
(find-buffer-visiting
(expand-file-name filename)))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (filename)
(concat
(bufferp (find-buffer-visiting (expand-file-name filename)))
"\n---\nTool execution complete. Proceed with next step."))
:name "find-buffer-visiting"
:function #'gptel-org-tools--find-buffer-visiting
:name "gptel-org-tools--find-buffer-visiting"
:description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
:args (list '(:name "filename"
:type string
@ -310,15 +317,15 @@ Disabled for now, as it's causing some issues.
**** open-file-inactive
Continuation from above. Open a file into a buffer for processing, once it's found by dired-list.
#+begin_src elisp
(defun gptel-org-tools--open-file-inactive (file)
(with-current-buffer (get-buffer-create file)
(insert-file-contents file)
(concat
(current-buffer))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (file)
(with-current-buffer (get-buffer-create file)
(insert-file-contents file)
(concat
(current-buffer)
"\n---\nTool execution complete. Proceed with next step.")))
:name "open-file-inactive"
:function #'gptel-org-tools--open-file-inactive
:name "gptel-org-tools--open-file-inactive"
:description "Open the file in a background buffer. This doesn't interfere with the user."
:args (list '(:name "file"
:type string
@ -329,15 +336,17 @@ Continuation from above. Open a file into a buffer for processing, once it's fou
**** read-file-contents
This reads file contents,
#+begin_src elisp :tangle no
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (filename)
(with-temp-buffer
(defun gptel-org-tools--read-file-contents (file)
(with-temp-buffer
(insert-file-contents (expand-file-name filename))
(concat
(buffer-string)
"\n---\nTool execution complete. Proceed with next step.")))
:name "read-file-contents"
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--read-file-contents
:name "gptel-org-tools--read-file-contents"
:description "Read and return the contents of a specified file."
:args (list '(:name "filename"
:type string
@ -347,14 +356,16 @@ This reads file contents,
#+end_src
**** describe-variable
#+begin_src elisp
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (var)
(let ((symbol (intern var)))
(defun gptel-org-tools--describe-variable (var)
(let ((symbol (intern var)))
(if (boundp symbol)
(prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound." var))))
:name "describe-variable"
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--describe-variable
:name "gptel-org-tools--describe-variable"
:description "See variable contents"
:args (list '(:name "var"
:type string
@ -364,14 +375,16 @@ This reads file contents,
**** describe-function
#+begin_src elisp
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (fun)
(let ((symbol (intern fun)))
(defun gptel-org-tools--describe-function (fun)
(let ((symbol (intern fun)))
(if (fboundp symbol)
(prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun))))
:name "describe-function"
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--describe-function
:name "gptel-org-tools--describe-function"
:description "See function description"
:args (list '(:name "fun"
:type string
@ -425,7 +438,7 @@ This is not, by any means, sufficient, but I do tag people and specific events f
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-extract-tags
:name "org-extract-tags"
:name "gptel-org-tools--org-extract-tags"
:description "Extract all unique tags from an org-mode buffer"
:args (list '(:name "buffer"
:type string
@ -442,16 +455,16 @@ Therefore, headings. A reasonable amount of information, and still keeping the s
(defun gptel-org-tools--org-extract-headings (buffer)
(if (member buffer gptel-org-tools-skip-heading-extraction)
(user-error "Buffer %s has too many headings, use org-extract-tags or org-ql-select-rifle." buffer)
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
t
'file))))
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
t
'file))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-extract-headings
:name "org-extract-headings"
:name "gptel-org-tools--org-extract-headings"
:description "Extract all headings from an org-mode buffer"
:args (list '(:name "buffer"
:type string
@ -485,7 +498,7 @@ Currently *not* tangled, as I'm testing breaking out each type of query into its
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select
:name "org-ql-select"
:name "gptel-org-tools--org-ql-select"
:description "Run org-ql-select against buffer with query. Using filename fails."
:args (list '(:name "buffer"
:type string
@ -516,7 +529,41 @@ Notice it pulls to the end of the subtree. So for months, that's at least 28 ent
But, any customizations to tweak this is left to the user, as everyone has their own conventions.
Testing new version:
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-by-date (buf date)
"Returns all timestamped headings matching the specified date or date range.
The date can be in the format YYYY, YYYY-MM, or YYYY-MM-DD.
BUFFER is the name of the buffer to search.
DATE is the date or date range to match."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
(if buffer
(if (eq mode 'org-mode)
(org-ql-select buffer
`(heading ,date)
:action #'gptel-org-tools--heading-subtree)
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-by-date
:name "gptel-org-tools--org-ql-select-by-date"
:description "Returns all timestamped headings matching query. Query may be: YYYY, YYYY-MM, YYYY-MM-DD."
:args (list '(:name "buffer"
:type string
:description "Buffer name.")
'(:name "date"
:type string
:description "Date in YYYY or YYYY-MM format."))
:category "org"))
#+end_src
Original (works but not ideal).
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select-by-date (buf date)
(org-ql-select
(get-buffer buf)
@ -527,7 +574,7 @@ But, any customizations to tweak this is left to the user, as everyone has their
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-by-date
:name "org-ql-select-by-date"
:name "gptel-org-tools--org-ql-select-by-date"
:description "Returns all timestamped headings matching query. Query may be: YYYY, YYYY-MM, YYYY-MM-DD. Prefer using this first when request specifies any time periods. Example: get all headings matching March 2025: \"2025-03\""
:args (list '(:name "buffer"
:type string
@ -542,16 +589,18 @@ This is still work in progress, the idea is to have the LLM check my calendar an
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
(defun gptel-org-tools--org-agenda-seek (days)
(with-temp-buffer
(org-agenda-list (or days 14))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
content)))
(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-seek"
:description "Get user's agenda/ tasking from now to X days in the past or future"
:function #'gptel-org-tools--org-agenda-seek
:name "gptel-org-tools--org-agenda-seek"
:description "Get user's agenda (tasking) spanning X days from now. This can be used to get all agenda tasks that are due or scheduled in the next X days, or in the past X days, depending on whether the days argument is positive or negative. Example: get all agenda tasks due in the next 7 days: \"7\""
:args (list '(:name "days"
:type integer
:description "Days. Positive = future. Negative = past. Default: 14"))
@ -566,7 +615,7 @@ The following tools are still very much WIP, and I think they're self-explanator
Retrieve the headings where the heading matches query..
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-headings (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(heading ,query)
:action #''gptel-org-tools--heading))
@ -575,14 +624,14 @@ Retrieve the headings where the heading matches query..
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings
:name "org-ql-select-headings"
:name "gptel-org-tools--org-ql-select-headings"
:description "Retreive matching headings from buffer. Matches only a single string. 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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
#+end_src
@ -590,23 +639,23 @@ Retrieve the headings where the heading matches query..
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 #'gptel-org-tools--heading))
(org-ql-select
(get-buffer buf)
`(rifle ,query)
:action #'gptel-org-tools--heading))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle
:name "org-ql-select-headings-rifle"
:name "gptel-org-tools--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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
#+end_src
@ -614,16 +663,16 @@ Retrieve all the headings where either heading or content matches query.
This pulls all the headings (and their contents) when they match tags (without inheritance.)
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-tags-local (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools-heading-body))
:action #'gptel-org-tools--heading-body))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "org-ql-select-tags-local"
:name "gptel-org-tools--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
@ -634,11 +683,35 @@ This pulls all the headings (and their contents) when they match tags (without i
:category "org-ql"))
#+end_src
***** org-ql-select-tags-local-count
This pulls all the local tags (without inheritance) from buffer, and returns the number of these tagged headings.
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-tags-local (buf query)
(length (org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body)))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "gptel-org-tools--org-ql-select-tags-local"
:description "Get count of matching tags from buffer. 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 string match entry tags against."))
:category "org-ql"))
#+end_src
***** org-ql-select-tags
This pulls all the headings (and their contents) when they match tags (with inheritance; if a parent entry has the tag, descendant entries do, too.)
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-tags (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(tags ,query)
:action #'gptel-org-tools--heading-body))
@ -646,14 +719,14 @@ This pulls all the headings (and their contents) when they match tags (with inhe
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags
:name "org-ql-select-tags"
:name "gptel-org-tools--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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
#+end_src
@ -661,7 +734,7 @@ This pulls all the headings (and their contents) when they match tags (with inhe
And, the "grab everything that matches" tool.
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-rifle (buf query)
(let ((buffer (get-buffer buf)))
(let ((buffer (get-buffer buf)))
(if buffer
(org-ql-select
buffer
@ -672,14 +745,14 @@ And, the "grab everything that matches" tool.
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle
:name "org-ql-select-rifle"
:name "gptel-org-tools--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 "A single keyword to search for."))
:description "A single string to search for."))
:category "org-ql"))
#+end_src
@ -696,8 +769,8 @@ This pulls all the headings (and their contents) when they match tags (without i
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags-local
:name "org-ql-select-all-tags-local"
:description "Run single word query against all files in (org-agenda-files). WITHOUT tag inheritance, only directly tagged headings."
:name "gptel-org-tools--org-ql-select-all-tags-local"
:description "Run single string query against all files in (org-agenda-files). WITHOUT tag inheritance, only directly tagged headings."
:args (list '(:name "query"
:type string
:description "A single word to scan for."))
@ -716,11 +789,11 @@ This pulls all the headings (and their contents) when they match tags (with inhe
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags
:name "org-ql-select-all-tags"
:description "Run single word query against all files in (org-agenda-files). WITH tag inheritance."
:name "gptel-org-tools--org-ql-select-all-tags"
:description "Run single string query against all files in (org-agenda-files). WITH tag inheritance."
:args (list '(:name "query"
:type string
:description "A single word to scan for."))
:description "A simple (single) string to scan for."))
:category "org-ql"))
#+end_src
@ -745,11 +818,11 @@ This means that /every org-mode file I have/ is part of this search.
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-rifle
:name "org-ql-select-all-rifle"
:description "Run single word query against ALL org-mode files (notes)."
:name "gptel-org-tools--org-ql-select-all-rifle"
:description "Run simple (single) string against ALL org-mode files (notes)."
:args (list '(:name "query"
:type string
:description "The keyword to match entry headings and content against."))
:description "The string to match entry headings and content against."))
:category "org-ql"))
#+end_src
@ -762,13 +835,11 @@ This means that /every org-mode file I have/ is part of this search.
(org-agenda-files)
`(regexp ,bound-query)
:action #'gptel-org-tools--heading-body)))
#+end_src
#+begin_src elisp
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-regexp
:name "org-ql-select-all-regexp"
:name "gptel-org-tools--org-ql-select-all-regexp"
:description "Run regexp on ALL files at once."
:args (list '(:name "query"
:type string

View file

@ -6,7 +6,7 @@
;; Keywords: extensions, comm, tools, matching, convenience,
;;
;; Author: Phil Bajsicki <phil@bajsicki.com>
;; Version: 0.0.1
;; Version: 0.0.2
;; Package-Requires: ((emacs "30.1") (gptel 0.9.8) (org-ql 0.9))
;; URL: https://github.com/phil/gptel-org-tools
;; SPDX-License-Identifier: GPL-3.0
@ -46,28 +46,30 @@
'"\n---\n"))
(defun gptel-org-tools--heading-body ()
(concat
(buffer-substring-no-properties
(line-beginning-position)
(progn
(outline-next-heading)
(line-beginning-position)))
"---\n"))
(concat
(buffer-substring-no-properties
(line-beginning-position)
(progn
(outline-next-heading)
(line-beginning-position)))
"---\n"))
(defun gptel-org-tools--heading-subtree ()
(concat
(buffer-substring-no-properties
(line-beginning-position)
(org-end-of-subtree))
"---\n"))
(concat
(buffer-substring-no-properties
(line-beginning-position)
(org-end-of-subtree))
"---\n"))
(defun gptel-org-tool--list-buffers ()
(list-buffers-noselect)
(with-temp-buffer "*Buffer List*"
(buffer-string)))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (arg)
(list-buffers-noselect)
(with-current-buffer "*Buffer List*"
(buffer-string)))
:name "list-buffers"
:function #'gptel-org-tool--list-buffers
:name "gptel-org-tool--list-buffers"
:description "Access the list of buffers open in Emacs, including file names and full paths."
:args (list '(:name "arg"
:type string
@ -75,15 +77,17 @@
:optional t))
:category "emacs"))
(defun gptel-org-tools--dir (dir)
(with-temp-buffer
(dired (or dir "~"))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
content)))
(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"
:function #'gptel-org-tools--dir
:name "gptel-org-tools--dir"
:description "List directory contents"
:args (list '(:name "dir"
:type string
@ -91,56 +95,47 @@
:optional t))
:category "filesystem"))
(defun gptel-org-tools--open-file-inactive (file)
(with-current-buffer (get-buffer-create file)
(insert-file-contents file)
(concat
(current-buffer))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (filename)
(concat
(bufferp (find-buffer-visiting (expand-file-name filename)))
"\n---\nTool execution complete. Proceed with next step."))
: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 (file)
(with-current-buffer (get-buffer-create file)
(insert-file-contents file)
(concat
(current-buffer)
"\n---\nTool execution complete. Proceed with next step.")))
:name "open-file-inactive"
:function #'gptel-org-tools--open-file-inactive
:name "gptel-org-tools--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 (var)
(let ((symbol (intern var)))
(defun gptel-org-tools--describe-variable (var)
(let ((symbol (intern var)))
(if (boundp symbol)
(prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound." var))))
:name "describe-variable"
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--describe-variable
:name "gptel-org-tools--describe-variable"
:description "See variable contents"
:args (list '(:name "var"
:type string
:description "Variable name"))
:category "emacs"))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function (lambda (fun)
(let ((symbol (intern fun)))
(defun gptel-org-tools--describe-function (fun)
(let ((symbol (intern fun)))
(if (fboundp symbol)
(prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun))))
:name "describe-function"
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--describe-function
:name "gptel-org-tools--describe-function"
:description "See function description"
:args (list '(:name "fun"
:type string
@ -163,7 +158,7 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-extract-tags
:name "org-extract-tags"
:name "gptel-org-tools--org-extract-tags"
:description "Extract all unique tags from an org-mode buffer"
:args (list '(:name "buffer"
:type string
@ -173,16 +168,16 @@
(defun gptel-org-tools--org-extract-headings (buffer)
(if (member buffer gptel-org-tools-skip-heading-extraction)
(user-error "Buffer %s has too many headings, use org-extract-tags or org-ql-select-rifle." buffer)
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
t
'file))))
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
t
'file))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-extract-headings
:name "org-extract-headings"
:name "gptel-org-tools--org-extract-headings"
:description "Extract all headings from an org-mode buffer"
:args (list '(:name "buffer"
:type string
@ -190,42 +185,54 @@
:category "org-mode"))
(defun gptel-org-tools--org-ql-select-by-date (buf date)
(org-ql-select
(get-buffer buf)
`(heading ,date)
:action #'gptel-org-tools--heading-subtree))
"Returns all timestamped headings matching the specified date or date range.
The date can be in the format YYYY, YYYY-MM, or YYYY-MM-DD.
BUFFER is the name of the buffer to search.
DATE is the date or date range to match."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
(if buffer
(if (eq mode 'org-mode)
(org-ql-select buffer
`(heading ,date)
:action #'gptel-org-tools--heading-subtree)
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-by-date
:name "org-ql-select-by-date"
:description "Returns all timestamped headings matching query. Query may be: YYYY, YYYY-MM, YYYY-MM-DD. Prefer using this first when request specifies any time periods. Example: get all headings matching March 2025: \"2025-03\""
:args (list '(:name "buffer"
:type string
:description "Buffer name.")
'(:name "date"
:type string
:description "Date in YYYY or YYYY-MM format."))
:category "org"))
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-by-date
:name "gptel-org-tools--org-ql-select-by-date"
:description "Returns all timestamped headings matching query. Query may be: YYYY, YYYY-MM, YYYY-MM-DD."
:args (list '(:name "buffer"
:type string
:description "Buffer name.")
'(:name "date"
:type string
:description "Date in YYYY or YYYY-MM format."))
:category "org"))
(defun gptel-org-tools--org-agenda-seek (days)
(with-temp-buffer
(org-agenda-list (or days 14))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
content)))
(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-seek"
:description "Get user's agenda/ tasking from now to X days in the past or future"
:function #'gptel-org-tools--org-agenda-seek
:name "gptel-org-tools--org-agenda-seek"
:description "Get user's agenda (tasking) spanning X days from now. This can be used to get all agenda tasks that are due or scheduled in the next X days, or in the past X days, depending on whether the days argument is positive or negative. Example: get all agenda tasks due in the next 7 days: \"7\""
:args (list '(:name "days"
:type integer
:description "Days. Positive = future. Negative = past. Default: 14"))
:category "org"))
(defun gptel-org-tools--org-ql-select-headings (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(heading ,query)
:action #''gptel-org-tools--heading))
@ -234,47 +241,47 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings
:name "org-ql-select-headings"
:name "gptel-org-tools--org-ql-select-headings"
:description "Retreive matching headings from buffer. Matches only a single string. 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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
(org-ql-select
(get-buffer buf)
`(rifle ,query)
:action #'gptel-org-tools--heading))
(org-ql-select
(get-buffer buf)
`(rifle ,query)
:action #'gptel-org-tools--heading))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle
:name "org-ql-select-headings-rifle"
:name "gptel-org-tools--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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-tags-local (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools-heading-body))
:action #'gptel-org-tools--heading-body))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "org-ql-select-tags-local"
:name "gptel-org-tools--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
@ -284,8 +291,28 @@
:description "The string match entry tags against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-tags-local (buf query)
(length (org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body)))
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "gptel-org-tools--org-ql-select-tags-local"
:description "Get count of matching tags from buffer. 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 string match entry tags against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-tags (buf query)
(org-ql-select
(org-ql-select
(get-buffer buf)
`(tags ,query)
:action #'gptel-org-tools--heading-body))
@ -293,18 +320,18 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags
:name "org-ql-select-tags"
:name "gptel-org-tools--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 keyword to match entry headings against."))
:description "The string to match entry headings against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-rifle (buf query)
(let ((buffer (get-buffer buf)))
(let ((buffer (get-buffer buf)))
(if buffer
(org-ql-select
buffer
@ -315,14 +342,14 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle
:name "org-ql-select-rifle"
:name "gptel-org-tools--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 "A single keyword to search for."))
:description "A single string to search for."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-all-tags-local (query)
@ -335,8 +362,8 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags-local
:name "org-ql-select-all-tags-local"
:description "Run single word query against all files in (org-agenda-files). WITHOUT tag inheritance, only directly tagged headings."
:name "gptel-org-tools--org-ql-select-all-tags-local"
:description "Run single string query against all files in (org-agenda-files). WITHOUT tag inheritance, only directly tagged headings."
:args (list '(:name "query"
:type string
:description "A single word to scan for."))
@ -351,11 +378,11 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags
:name "org-ql-select-all-tags"
:description "Run single word query against all files in (org-agenda-files). WITH tag inheritance."
:name "gptel-org-tools--org-ql-select-all-tags"
:description "Run single string query against all files in (org-agenda-files). WITH tag inheritance."
:args (list '(:name "query"
:type string
:description "A single word to scan for."))
:description "A simple (single) string to scan for."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-all-rifle (query)
@ -367,11 +394,11 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-rifle
:name "org-ql-select-all-rifle"
:description "Run single word query against ALL org-mode files (notes)."
:name "gptel-org-tools--org-ql-select-all-rifle"
:description "Run simple (single) string against ALL org-mode files (notes)."
:args (list '(:name "query"
:type string
:description "The keyword to match entry headings and content against."))
:description "The string to match entry headings and content against."))
:category "org-ql"))
(defun gptel-org-tools--org-ql-select-all-regexp (query)
@ -384,7 +411,7 @@
(add-to-list 'gptel-org-tools
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-regexp
:name "org-ql-select-all-regexp"
:name "gptel-org-tools--org-ql-select-all-regexp"
:description "Run regexp on ALL files at once."
:args (list '(:name "query"
:type string