Change name

This commit is contained in:
Phil Bajsicki 2025-05-25 16:25:38 +02:00
parent 7a4ac1dab3
commit bb70c340c5
2 changed files with 235 additions and 235 deletions

View file

@ -1,11 +1,11 @@
#+title: gptel-org-tools
#+title: gptel-org
#+author: Phil Bajsicki
#+auto_tangle: t
#+PROPERTY: header-args :elisp :tangle gptel-org-tools.el
#+PROPERTY: header-args :elisp :tangle gptel-org.el
* Notes
1. This repository previously lived on my personal forge. I have since moved it to codeberg. My personal forge will remain reasonably up-to-date, however [[https://codeberg.org/bajsicki/gptel-org-tools][codeberg]] is the reference repository.
2. The =gptel.org= file exists for tracking issues in my set-up, and is fundamentally a way for me to maintain some semblance of sanity in testing, as GitHub/ forges aren't really easy to work with for this.
3. [2025-05-25 Sun] Package renamed to gptel-org, since typing =-tools= is a mess.
* Disclaimer
- ~This package is an active privacy risk. It allows the LLM to autonomously expand its context in any direction it chooses.~
- ~Only connect to third-party systems if you understand and accept the risk of *any* of your files becoming publicly accessible.~
@ -14,7 +14,7 @@ This is a collection of tools I wrote to help me review my life.
Summary:
- An explanation of each tool is below.
- The tools are tangled into =gptel-org-tools.el= *from this file*.
- The tools are tangled into =gptel-org.el= *from this file*.
Premise:
- LLMs are /not very smart/ and /unreliable/, but they do okay with basic text comprehension, and can generate completions/ responses which /fit the vibe/ of the request.
@ -41,15 +41,15 @@ I only use Doom Emacs, so here's how I load:
packages.el:
#+begin_src elisp :tangle no
(package! gptel-org-tools
(package! gptel-org
:recipe (:host nil
:repo "https://codeberg.org/bajsicki/gptel-org-tools"))
:repo "https://codeberg.org/bajsicki/gptel-org"))
#+end_src
config.el:
#+begin_src elisp :tangle no
(require 'gptel-org-tools)
(setq gptel-tools gptel-org-tools)
(require 'gptel-org)
(setq gptel-tools gptel-org)
#+end_src
This /will/ overwrite any other tools you have defined before this call takes place.
@ -58,8 +58,8 @@ If you want to just append them to your existing tool list:
config.el:
#+begin_src elisp :tangle no
(require 'gptel-org-tools)
(mapcar (lambda (tool) (cl-pushnew tool gptel-tools)) gptel-org-tools)
(require 'gptel-org)
(mapcar (lambda (tool) (cl-pushnew tool gptel-tools)) gptel-org)
#+end_src
* Code
** Preamble
@ -68,7 +68,7 @@ config.el:
:END:
Stuff, headers, etc.
#+begin_src elisp
;;; gptel-org-tools.el --- LLM Tools for org-mode interaction. -*- lexical-binding: t; -*-
;;; gptel-org.el --- LLM Tools for org-mode interaction. -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Phil Bajsicki
@ -78,7 +78,7 @@ Stuff, headers, etc.
;; Author: Phil Bajsicki <phil@bajsicki.com>
;; 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
;; URL: https://github.com/phil/gptel-org
;; SPDX-License-Identifier: GPL-3.0
;; This program is free software; you can redistribute it and/or modify
@ -100,14 +100,14 @@ Stuff, headers, etc.
;;; Commentary:
;; All documentation regarding these functions is in the README.org file.
;; Repository: https://git.bajsicki.com/phil/gptel-org-tools
;; Repository: https://git.bajsicki.com/phil/gptel-org
;;; Code:)
#+end_src
** gptel-org-tools
Collects into =gptel-org-tools= list, distinct from =gptel-tools=
** gptel-org
Collects into =gptel-org= list, distinct from =gptel-tools=
#+begin_src elisp
(defvar gptel-org-tools '())
(defvar gptel-org '())
#+end_src
** Variables
*** skip-heading-extraction
@ -116,13 +116,13 @@ If you're like me, you may have structured headings based on timestamps in your
To that end, there are a few variables that may help you from blowing out the context, specifically for the ~org-extract-headings~ tool.
#+begin_src elisp
(defvar gptel-org-tools-skip-heading-extraction '())
(defvar gptel-org-skip-heading-extraction '())
#+end_src
Use ~setq~ in your configuration, with a list of buffer names whose headings you *never* want to extract. E.g.
#+begin_src elisp :tangle no
(setq gptel-org-tools-skip-heading-extraction '("journal.org" ".org"))
(setq gptel-org-skip-heading-extraction '("journal.org" ".org"))
#+end_src
*** result-limit
@ -137,11 +137,11 @@ To that end, the stop-gap solution is to implement a hard limit on the amount of
I don't think there's much reason to ever allow partial results, which leads me to this:
#+begin_src elisp :results none
(defvar gptel-org-tools-result-limit 40000)
(defvar gptel-org-result-limit 40000)
(defun gptel-org-tools--result-limit (result)
(if (>= (length (format "%s" result)) gptel-org-tools-result-limit)
(format "Results over %s character. Stop. Analyze. Find a different solution, or use a more specific query." gptel-org-tools-result-limit)
(defun gptel-org--result-limit (result)
(if (>= (length (format "%s" result)) gptel-org-result-limit)
(format "Results over %s character. Stop. Analyze. Find a different solution, or use a more specific query." gptel-org-result-limit)
result))
@ -150,7 +150,7 @@ I don't think there's much reason to ever allow partial results, which leads me
Use ~setq~ in your configuration, e.g.:
#+begin_src elisp :tangle no :results none
(setq gptel-org-tools-result-limit 40000)
(setq gptel-org-result-limit 40000)
#+end_src
This will *prevent* tools from returning results longer than 40,000 characters. Instead, the LLM will receive a message saying it should be much more specific in its queries, which will hopefully guide it to be more specific.
@ -163,7 +163,7 @@ These abstract away some of the tool definitions. They're called from each funct
*** Return heading (line)
#+begin_src elisp
(defun gptel-org-tools--heading ()
(defun gptel-org--heading ()
"Return the org-mode heading."
(concat
(buffer-substring-no-properties
@ -173,7 +173,7 @@ These abstract away some of the tool definitions. They're called from each funct
*** Return heading and body (without subheadings)
#+begin_src elisp
(defun gptel-org-tools--heading-body ()
(defun gptel-org--heading-body ()
"Return the org-mode heading and body text."
(concat
(buffer-substring-no-properties
@ -185,7 +185,7 @@ These abstract away some of the tool definitions. They're called from each funct
*** Return heading and subheadings (until next same-level heading)
#+begin_src elisp
(defun gptel-org-tools--heading-subtree ()
(defun gptel-org--heading-subtree ()
"Return the org-mode heading and all subheadings, with their body text."
(concat
(buffer-substring-no-properties
@ -204,16 +204,16 @@ 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)
(defun gptel-org--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
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--eval
:name "gptel-org-tools--eval"
:function #'gptel-org--eval
:name "gptel-org--eval"
:args (list '(:name "eval"
:type string
:description "The Emacs Lisp code to evaluate."))
@ -232,7 +232,7 @@ Not using ~ibuffer~ to avoid customization differences between users. Argument r
(let ((content (buffer-string)))
content)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tool--list-buffers
:name "gptel-org-tool--list-buffers"
@ -252,7 +252,7 @@ You can customize the function to point to your org directory, if you wish. I'm
#+end_comment
#+begin_src elisp
(defun gptel-org-tools--dir (dir)
(defun gptel-org--dir (dir)
"Return directory listing."
(with-temp-buffer
(dired (or dir "~"))
@ -260,10 +260,10 @@ You can customize the function to point to your org directory, if you wish. I'm
(kill-buffer (current-buffer))
content)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--dir
:name "gptel-org-tools--dir"
:function #'gptel-org--dir
:name "gptel-org--dir"
:description "List directory contents. After using this, stop. Evaluate for relevance. Then use your findings to fulfill user's request."
:args (list '(:name "dir"
:type string
@ -275,17 +275,17 @@ You can customize the function to point to your org directory, if you wish. I'm
**** find-buffer-visiting [disabled by default]
Disabled for now, as it's causing some issues.
#+begin_src elisp :tangle no :results none
(defun gptel-org-tools--find-buffer-visiting (filename)
(defun gptel-org--find-buffer-visiting (filename)
"Return the buffer visiting file FILENAME."
(concat
(bufferp
(find-buffer-visiting
(expand-file-name filename)))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--find-buffer-visiting
:name "gptel-org-tools--find-buffer-visiting"
:function #'gptel-org--find-buffer-visiting
:name "gptel-org--find-buffer-visiting"
:description "Check if the file is open in a buffer. Usage (find-buffer-visiting filename)"
:args (list '(:name "filename"
:type string
@ -296,15 +296,15 @@ Disabled for now, as it's causing some issues.
**** open-file-inactive
Opens a file into an inactive (background) buffer for processing.
#+begin_src elisp
(defun gptel-org-tools--open-file-inactive (file)
(defun gptel-org--open-file-inactive (file)
"Open FILE in a background buffer without modifying its contents."
(find-file-noselect file)
(set-buffer-modified-p nil))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--open-file-inactive
:name "gptel-org-tools--open-file-inactive"
:function #'gptel-org--open-file-inactive
:name "gptel-org--open-file-inactive"
:description "Open the file in a background buffer. This does NOT return the contents of the file. After calling this tool, stop. Then continue fulfilling user's request."
:args (list '(:name "file"
:type string
@ -314,17 +314,17 @@ Opens a file into an inactive (background) buffer for processing.
**** read-file-contents
#+begin_src elisp :tangle no
(defun gptel-org-tools--read-file-contents (file)
(defun gptel-org--read-file-contents (file)
"Return contents of FILE."
(with-temp-buffer
(insert-file-contents (expand-file-name filename))
(concat
(buffer-string))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--read-file-contents
:name "gptel-org-tools--read-file-contents"
:function #'gptel-org--read-file-contents
:name "gptel-org--read-file-contents"
:description "Reads and return the contents of a specified file. After calling this tool, stop. Then continue fulfilling user's request."
:args (list '(:name "filename"
:type string
@ -334,17 +334,17 @@ Opens a file into an inactive (background) buffer for processing.
#+end_src
**** describe-variable
#+begin_src elisp
(defun gptel-org-tools--describe-variable (var)
(defun gptel-org--describe-variable (var)
"Return documentation for VAR."
(let ((symbol (intern var)))
(if (boundp symbol)
(prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound. This means the variable doesn't exist. Stop. Reassess what you're trying to do, examine the situation, and continue. " var))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--describe-variable
:name "gptel-org-tools--describe-variable"
:function #'gptel-org--describe-variable
:name "gptel-org--describe-variable"
:description "Returns variable contents. After calling this tool, stop. Evaluate if the result helps. Then continue fulfilling user's request."
:args (list '(:name "var"
:type string
@ -354,17 +354,17 @@ Opens a file into an inactive (background) buffer for processing.
**** describe-function
#+begin_src elisp
(defun gptel-org-tools--describe-function (fun)
(defun gptel-org--describe-function (fun)
"Return documentation for FUN."
(let ((symbol (intern fun)))
(if (fboundp symbol)
(prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--describe-function
:name "gptel-org-tools--describe-function"
:function #'gptel-org--describe-function
:name "gptel-org--describe-function"
:description "Returns function description. After calling this tool, stop. Evaluate if the result helps. Then continue fulfilling user's request."
:args (list '(:name "fun"
:type string
@ -388,7 +388,7 @@ LLMs are not intelligent, despite claims to the contrary.
**** org-extract-tags
#+begin_src elisp
(defun gptel-org-tools--org-extract-tags (buffer)
(defun gptel-org--org-extract-tags (buffer)
"Return all tags from BUFFER."
(with-current-buffer buffer
(let ((tags '()))
@ -401,10 +401,10 @@ LLMs are not intelligent, despite claims to the contrary.
(push tag tags))))))
(sort (-uniq tags) #'string<))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-extract-tags
:name "gptel-org-tools--org-extract-tags"
:function #'gptel-org--org-extract-tags
:name "gptel-org--org-extract-tags"
:args (list '(:name "buffer"
:type string
:description "The Org buffer to extract tags from."))
@ -413,20 +413,20 @@ LLMs are not intelligent, despite claims to the contrary.
#+end_src
**** org-extract-headings
#+begin_src elisp
(defun gptel-org-tools--org-extract-headings (buffer)
(defun gptel-org--org-extract-headings (buffer)
"Return all headings from BUFFER."
(if (member buffer gptel-org-tools-skip-heading-extraction)
(if (member buffer gptel-org-skip-heading-extraction)
(user-error "Buffer %s has been blocked from this function by the user with reason: headings contain timestamps, no useful information. Use a different tool." buffer)
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
#'gptel-org--heading
t
'file))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-extract-headings
:name "gptel-org-tools--org-extract-headings"
:function #'gptel-org--org-extract-headings
:name "gptel-org--org-extract-headings"
:description "Returns all headings from an org-mode buffer. After using this, stop. Then evaluate the relevance of the headings to the user's request. Then continue."
:args (list '(:name "buffer"
:type string
@ -439,7 +439,7 @@ Ugly. LLMs sometimes return a sexp, and sometimes a quoted string for =query=. S
In general, this is deprecated, but left here for historical reasons. I may resurrect this if I find a good way of prompting the LLM to structure queries well.
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select (buf query)
(defun gptel-org--org-ql-select (buf query)
"Return entries matching QUERY from BUFFER.
QUERY can be any valid org-ql-select query."
@ -448,13 +448,13 @@ QUERY can be any valid org-ql-select query."
(if (stringp query)
(read query)
query
:action #'gptel-org-tools--heading-body))))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body))))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select
:name "gptel-org-tools--org-ql-select"
:function #'gptel-org--org-ql-select
:name "gptel-org--org-ql-select"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. Can be multiple buffers. See the NAME column in `list-buffers`.")
@ -483,7 +483,7 @@ The reason for this tool existing (it's the same as select-heading), is that it
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-date (buf date)
(defun gptel-org--org-ql-select-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.
@ -496,20 +496,20 @@ DATE is the date or date range to match."
(let ((result
(org-ql-select buffer
`(heading ,date)
:action #'gptel-org-tools--heading-subtree)))
(concat (gptel-org-tools--result-limit result) "Results end here. Proceed with the next action."))
:action #'gptel-org--heading-subtree)))
(concat (gptel-org--result-limit result) "Results end here. Proceed with the next action."))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
#+end_src
#+RESULTS:
: gptel-org-tools--org-ql-select-date
: gptel-org--org-ql-select-date
#+begin_src elisp
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-date
:name "gptel-org-tools--org-ql-select-date"
:function #'gptel-org--org-ql-select-date
:name "gptel-org--org-ql-select-date"
:args (list '(:name "buffer"
:type string
:description "Buffer name.")
@ -530,18 +530,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)
(defun gptel-org--org-agenda-seek (days)
"Return the results of org-agenda-list spanning now to DAYS."
(with-temp-buffer
(org-agenda-list (or days 14))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
(gptel-org-tools--result-limit content))))
(gptel-org--result-limit content))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-agenda-seek
:name "gptel-org-tools--org-agenda-seek"
:function #'gptel-org--org-agenda-seek
:name "gptel-org--org-agenda-seek"
:args (list '(:name "days"
:type integer
:description "Days. Positive = future. Negative = past. Default: 14"))
@ -555,7 +555,7 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
***** org-ql-select-headings
Retrieve the headings where the heading matches query. This is very much the same as ~org-ql-select-by-date~, but the descriptions are different to ensure the LLM knows what to do.
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-headings (buf query)
(defun gptel-org--org-ql-select-headings (buf query)
"Return headings matching QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -565,17 +565,17 @@ Retrieve the headings where the heading matches query. This is very much the sam
(org-ql-select
(get-buffer buf)
`(heading ,query)
:action #''gptel-org-tools--heading)))
(concat (gptel-org-tools--result-limit result) "Results end here. Proceed with the next action."))
:action #''gptel-org--heading)))
(concat (gptel-org--result-limit result) "Results end here. Proceed with the next action."))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings
:name "gptel-org-tools--org-ql-select-headings"
:function #'gptel-org--org-ql-select-headings
:name "gptel-org--org-ql-select-headings"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in ~list-buffers~.")
@ -589,20 +589,20 @@ Retrieve the headings where the heading matches query. This is very much the sam
***** 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)
(defun gptel-org--org-ql-select-headings-rifle (buf query)
"Return headings of entries (body included) that match keyword QUERY from BUFFER."
(let ((result
(org-ql-select
(get-buffer buf)
`(rifle ,query)
:action #'gptel-org-tools--heading)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle
:name "gptel-org-tools--org-ql-select-headings-rifle"
:function #'gptel-org--org-ql-select-headings-rifle
:name "gptel-org--org-ql-select-headings-rifle"
:description "Returns headings matching QUERY from BUFFER. Matches against both heading and content, but only returns headings. After using this, stop. Evaluate results. Then continue completing user's request."
:args (list '(:name "buffer"
:type string
@ -626,16 +626,16 @@ This pulls all the headings (and their contents) when they match tags (without i
(org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "gptel-org-tools--org-ql-select-tags-local"
:function #'gptel-org--org-ql-select-tags-local
:name "gptel-org--org-ql-select-tags-local"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -649,7 +649,7 @@ This pulls all the headings (and their contents) when they match tags (without i
***** 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-count (buf query)
(defun gptel-org--org-ql-select-tags-local-count (buf query)
"Return count of entries tagged QUERY in BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -658,14 +658,14 @@ This pulls all the local tags (without inheritance) from buffer, and returns the
(length (org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body))
:action #'gptel-org--heading-body))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local-count
:name "gptel-org-tools--org-ql-select-tags-local"
:function #'gptel-org--org-ql-select-tags-local-count
:name "gptel-org--org-ql-select-tags-local"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -679,7 +679,7 @@ This pulls all the local tags (without inheritance) from buffer, and returns the
***** 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)
(defun gptel-org--org-ql-select-tags (buf query)
"Return every entry tagged QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -689,15 +689,15 @@ This pulls all the headings (and their contents) when they match tags (with inhe
(org-ql-select
(get-buffer buf)
`(tags ,query)
:action #'gptel-org-tools--heading-body))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body))
(gptel-org--result-limit result)))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags
:name "gptel-org-tools--org-ql-select-tags"
:function #'gptel-org--org-ql-select-tags
:name "gptel-org--org-ql-select-tags"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -711,7 +711,7 @@ This pulls all the headings (and their contents) when they match tags (with inhe
***** org-ql-select-rifle
And, the "grab everything that matches" tool.
#+begin_src elisp
(defun gptel-org-tools--org-ql-select-rifle (buf query)
(defun gptel-org--org-ql-select-rifle (buf query)
"Return every entry matching keyword QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -721,15 +721,15 @@ And, the "grab everything that matches" tool.
(org-ql-select
buffer
`(rifle ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle
:name "gptel-org-tools--org-ql-select-rifle"
:function #'gptel-org--org-ql-select-rifle
:name "gptel-org--org-ql-select-rifle"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -742,7 +742,7 @@ And, the "grab everything that matches" tool.
***** org-ql-select-regexp
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select-regexp (buf query)
(defun gptel-org--org-ql-select-regexp (buf query)
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
(if buffer
@ -751,16 +751,16 @@ And, the "grab everything that matches" tool.
(org-ql-select
buffer
`(regexp ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-regexp
:name "gptel-org-tools--org-ql-select-regexp"
:function #'gptel-org--org-ql-select-regexp
:name "gptel-org--org-ql-select-regexp"
:description "Run regexp on ALL files at once."
:args (list '(:name "buffer"
:type string
@ -775,21 +775,21 @@ And, the "grab everything that matches" tool.
***** org-ql-select-all-tags-local
This pulls all the headings (and their contents) when they match tags (without inheritance.)
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select-all-tags-local (query)
(defun gptel-org--org-ql-select-all-tags-local (query)
"Return entries whose tags match QUERY in org-agenda-files.
QUERY is the tag to search for."
(let ((result
(org-ql-select
(org-agenda-files)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags-local
:name "gptel-org-tools--org-ql-select-all-tags-local"
:function #'gptel-org--org-ql-select-all-tags-local
:name "gptel-org--org-ql-select-all-tags-local"
:args (list '(:name "query"
:type string
:description "A single word to scan for."))
@ -800,7 +800,7 @@ This pulls all the headings (and their contents) when they match tags (without i
***** org-ql-select-all-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 :tangle no
(defun gptel-org-tools--org-ql-select-all-tags (query)
(defun gptel-org--org-ql-select-all-tags (query)
"Return entries whose tags match QUERY,
with inheritance, in org-agenda-files.
QUERY is the tag to search for."
@ -808,13 +808,13 @@ with inheritance, in org-agenda-files.
(org-ql-select
(org-agenda-files)
`(tags ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-tags
:name "gptel-org-tools--org-ql-select-all-tags"
:function #'gptel-org--org-ql-select-all-tags
:name "gptel-org--org-ql-select-all-tags"
:args (list '(:name "query"
:type string
:description "A simple (single) string to scan for."))
@ -834,20 +834,20 @@ Note that I define my agenda in this way:
This means that /every org-mode file I have/ is part of this search. If you're using a different set-up, or want to only use the LLM on specific files, then you should modify this function appropriately.
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select-all-rifle (query)
(defun gptel-org--org-ql-select-all-rifle (query)
"Return entries containing QUERY from org-agenda-files.
QUERY is the keyword to search for."
(let ((result
(org-ql-select
(org-agenda-files)
`(rifle ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-rifle
:name "gptel-org-tools--org-ql-select-all-rifle"
:function #'gptel-org--org-ql-select-all-rifle
:name "gptel-org--org-ql-select-all-rifle"
:description "Run simple (single) string against ALL org-mode files (notes)."
:args (list '(:name "query"
:type string
@ -859,20 +859,20 @@ This means that /every org-mode file I have/ is part of this search. If you're u
***** org-ql-select-all-regexp
#+begin_src elisp :tangle no
(defun gptel-org-tools--org-ql-select-all-regexp (query)
(defun gptel-org--org-ql-select-all-regexp (query)
"Return all entries matching regexp QUERY in org-agenda-files.
QUERY is a regular expression."
(let ((result
(org-ql-select
(org-agenda-files)
`(regexp ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-all-regexp
:name "gptel-org-tools--org-ql-select-all-regexp"
:function #'gptel-org--org-ql-select-all-regexp
:name "gptel-org--org-ql-select-all-regexp"
:description "Run regexp on ALL files at once."
:args (list '(:name "query"
:type string
@ -883,6 +883,6 @@ This means that /every org-mode file I have/ is part of this search. If you're u
** End
#+begin_src elisp
(provide 'gptel-org-tools)
;;; gptel-org-tools.el ends here
(provide 'gptel-org)
;;; gptel-org.el ends here
#+end_src

View file

@ -1,4 +1,4 @@
;;; gptel-org-tools.el --- LLM Tools for org-mode interaction. -*- lexical-binding: t; -*-
;;; gptel-org.el --- LLM Tools for org-mode interaction. -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Phil Bajsicki
@ -8,7 +8,7 @@
;; Author: Phil Bajsicki <phil@bajsicki.com>
;; 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
;; URL: https://github.com/phil/gptel-org
;; SPDX-License-Identifier: GPL-3.0
;; This program is free software; you can redistribute it and/or modify
@ -30,29 +30,29 @@
;;; Commentary:
;; All documentation regarding these functions is in the README.org file.
;; Repository: https://git.bajsicki.com/phil/gptel-org-tools
;; Repository: https://git.bajsicki.com/phil/gptel-org
;;; Code:)
(defvar gptel-org-tools '())
(defvar gptel-org '())
(defvar gptel-org-tools-skip-heading-extraction '())
(defvar gptel-org-skip-heading-extraction '())
(defvar gptel-org-tools-result-limit 40000)
(defvar gptel-org-result-limit 40000)
(defun gptel-org-tools--result-limit (result)
(if (>= (length (format "%s" result)) gptel-org-tools-result-limit)
(format "Results over %s character. Stop. Analyze. Find a different solution, or use a more specific query." gptel-org-tools-result-limit)
(defun gptel-org--result-limit (result)
(if (>= (length (format "%s" result)) gptel-org-result-limit)
(format "Results over %s character. Stop. Analyze. Find a different solution, or use a more specific query." gptel-org-result-limit)
result))
(defun gptel-org-tools--heading ()
(defun gptel-org--heading ()
"Return the org-mode heading."
(concat
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
(defun gptel-org-tools--heading-body ()
(defun gptel-org--heading-body ()
"Return the org-mode heading and body text."
(concat
(buffer-substring-no-properties
@ -61,7 +61,7 @@
(outline-next-heading)
(line-beginning-position)))))
(defun gptel-org-tools--heading-subtree ()
(defun gptel-org--heading-subtree ()
"Return the org-mode heading and all subheadings, with their body text."
(concat
(buffer-substring-no-properties
@ -75,7 +75,7 @@
(let ((content (buffer-string)))
content)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tool--list-buffers
:name "gptel-org-tool--list-buffers"
@ -86,7 +86,7 @@
:category "emacs"
:description "List buffers open in Emacs, including file names and full paths. After using this, stop. Then evaluate which files are most likely to be relevant to the user's request."))
(defun gptel-org-tools--dir (dir)
(defun gptel-org--dir (dir)
"Return directory listing."
(with-temp-buffer
(dired (or dir "~"))
@ -94,10 +94,10 @@
(kill-buffer (current-buffer))
content)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--dir
:name "gptel-org-tools--dir"
:function #'gptel-org--dir
:name "gptel-org--dir"
:description "List directory contents. After using this, stop. Evaluate for relevance. Then use your findings to fulfill user's request."
:args (list '(:name "dir"
:type string
@ -105,49 +105,49 @@
:optional t))
:category "filesystem"))
(defun gptel-org-tools--open-file-inactive (file)
(defun gptel-org--open-file-inactive (file)
"Open FILE in a background buffer without modifying its contents."
(find-file-noselect file)
(set-buffer-modified-p nil))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--open-file-inactive
:name "gptel-org-tools--open-file-inactive"
:function #'gptel-org--open-file-inactive
:name "gptel-org--open-file-inactive"
:description "Open the file in a background buffer. This does NOT return the contents of the file. After calling this tool, stop. Then continue fulfilling user's request."
:args (list '(:name "file"
:type string
:description "Path to file.."))
:category "filesystem"))
(defun gptel-org-tools--describe-variable (var)
(defun gptel-org--describe-variable (var)
"Return documentation for VAR."
(let ((symbol (intern var)))
(if (boundp symbol)
(prin1-to-string (symbol-value symbol))
(format "Variable %s is not bound. This means the variable doesn't exist. Stop. Reassess what you're trying to do, examine the situation, and continue. " var))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--describe-variable
:name "gptel-org-tools--describe-variable"
:function #'gptel-org--describe-variable
:name "gptel-org--describe-variable"
:description "Returns variable contents. After calling this tool, stop. Evaluate if the result helps. Then continue fulfilling user's request."
:args (list '(:name "var"
:type string
:description "Variable name"))
:category "emacs"))
(defun gptel-org-tools--describe-function (fun)
(defun gptel-org--describe-function (fun)
"Return documentation for FUN."
(let ((symbol (intern fun)))
(if (fboundp symbol)
(prin1-to-string (documentation symbol 'function))
(format "Function %s is not defined." fun))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--describe-function
:name "gptel-org-tools--describe-function"
:function #'gptel-org--describe-function
:name "gptel-org--describe-function"
:description "Returns function description. After calling this tool, stop. Evaluate if the result helps. Then continue fulfilling user's request."
:args (list '(:name "fun"
:type string
@ -155,7 +155,7 @@
:optional t))
:category "emacs"))
(defun gptel-org-tools--org-extract-tags (buffer)
(defun gptel-org--org-extract-tags (buffer)
"Return all tags from BUFFER."
(with-current-buffer buffer
(let ((tags '()))
@ -168,37 +168,37 @@
(push tag tags))))))
(sort (-uniq tags) #'string<))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-extract-tags
:name "gptel-org-tools--org-extract-tags"
:function #'gptel-org--org-extract-tags
:name "gptel-org--org-extract-tags"
:args (list '(:name "buffer"
:type string
:description "The Org buffer to extract tags from."))
:category "org-mode"
:description "Returns all tags from an org-mode buffer. When using this, evaluate the relevance of each tag to the user's request. After calling this tool, stop. Then continue fulfilling user's request."))
(defun gptel-org-tools--org-extract-headings (buffer)
(defun gptel-org--org-extract-headings (buffer)
"Return all headings from BUFFER."
(if (member buffer gptel-org-tools-skip-heading-extraction)
(if (member buffer gptel-org-skip-heading-extraction)
(user-error "Buffer %s has been blocked from this function by the user with reason: headings contain timestamps, no useful information. Use a different tool." buffer)
(with-current-buffer buffer
(org-map-entries
#'gptel-org-tools--heading
#'gptel-org--heading
t
'file))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-extract-headings
:name "gptel-org-tools--org-extract-headings"
:function #'gptel-org--org-extract-headings
:name "gptel-org--org-extract-headings"
:description "Returns all headings from an org-mode buffer. After using this, stop. Then evaluate the relevance of the headings to the user's request. Then continue."
:args (list '(:name "buffer"
:type string
:description "The Org buffer to extract headings from."))
:category "org-mode"))
(defun gptel-org-tools--org-ql-select-date (buf date)
(defun gptel-org--org-ql-select-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.
@ -211,15 +211,15 @@ DATE is the date or date range to match."
(let ((result
(org-ql-select buffer
`(heading ,date)
:action #'gptel-org-tools--heading-subtree)))
(concat (gptel-org-tools--result-limit result) "Results end here. Proceed with the next action."))
:action #'gptel-org--heading-subtree)))
(concat (gptel-org--result-limit result) "Results end here. Proceed with the next action."))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-date
:name "gptel-org-tools--org-ql-select-date"
:function #'gptel-org--org-ql-select-date
:name "gptel-org--org-ql-select-date"
:args (list '(:name "buffer"
:type string
:description "Buffer name.")
@ -234,18 +234,18 @@ Examples:
After using this, stop. Then evaluate the relevance of the entries to the user's request. Then continue."))
(defun gptel-org-tools--org-agenda-seek (days)
(defun gptel-org--org-agenda-seek (days)
"Return the results of org-agenda-list spanning now to DAYS."
(with-temp-buffer
(org-agenda-list (or days 14))
(let ((content (buffer-string)))
(kill-buffer (current-buffer))
(gptel-org-tools--result-limit content))))
(gptel-org--result-limit content))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-agenda-seek
:name "gptel-org-tools--org-agenda-seek"
:function #'gptel-org--org-agenda-seek
:name "gptel-org--org-agenda-seek"
:args (list '(:name "days"
:type integer
:description "Days. Positive = future. Negative = past. Default: 14"))
@ -255,7 +255,7 @@ After using this, stop. Then evaluate the relevance of the entries to the user's
- Get all tasks in the last 14 days: \"-14\"
After using this, stop. Evaluate the relevance of the results. Then continue."))
(defun gptel-org-tools--org-ql-select-headings (buf query)
(defun gptel-org--org-ql-select-headings (buf query)
"Return headings matching QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -265,17 +265,17 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
(org-ql-select
(get-buffer buf)
`(heading ,query)
:action #''gptel-org-tools--heading)))
(concat (gptel-org-tools--result-limit result) "Results end here. Proceed with the next action."))
:action #''gptel-org--heading)))
(concat (gptel-org--result-limit result) "Results end here. Proceed with the next action."))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings
:name "gptel-org-tools--org-ql-select-headings"
:function #'gptel-org--org-ql-select-headings
:name "gptel-org--org-ql-select-headings"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in ~list-buffers~.")
@ -285,20 +285,20 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
:category "org-ql"
:description "Returns entries matching QUERY from BUFFER. Matches only a single string. After using this, evaluate which entries are relevant, and continue with user's request."))
(defun gptel-org-tools--org-ql-select-headings-rifle (buf query)
(defun gptel-org--org-ql-select-headings-rifle (buf query)
"Return headings of entries (body included) that match keyword QUERY from BUFFER."
(let ((result
(org-ql-select
(get-buffer buf)
`(rifle ,query)
:action #'gptel-org-tools--heading)))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading)))
(gptel-org--result-limit result)))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-headings-rifle
:name "gptel-org-tools--org-ql-select-headings-rifle"
:function #'gptel-org--org-ql-select-headings-rifle
:name "gptel-org--org-ql-select-headings-rifle"
:description "Returns headings matching QUERY from BUFFER. Matches against both heading and content, but only returns headings. After using this, stop. Evaluate results. Then continue completing user's request."
:args (list '(:name "buffer"
:type string
@ -318,16 +318,16 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
(org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local
:name "gptel-org-tools--org-ql-select-tags-local"
:function #'gptel-org--org-ql-select-tags-local
:name "gptel-org--org-ql-select-tags-local"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -337,7 +337,7 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
:category "org-ql"
:description "Returns entries whose tags match QUERY from BUFFER, without tag inheritance. After using this, stop. Evaluate results for relevance, then proceed with completing user's request."))
(defun gptel-org-tools--org-ql-select-tags-local-count (buf query)
(defun gptel-org--org-ql-select-tags-local-count (buf query)
"Return count of entries tagged QUERY in BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -346,14 +346,14 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
(length (org-ql-select
(get-buffer buf)
`(tags-local ,query)
:action #'gptel-org-tools--heading-body))
:action #'gptel-org--heading-body))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags-local-count
:name "gptel-org-tools--org-ql-select-tags-local"
:function #'gptel-org--org-ql-select-tags-local-count
:name "gptel-org--org-ql-select-tags-local"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -363,7 +363,7 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
:category "org-ql"
:description "Returns count of entries tagged with tag QUERY from BUFFER, without tag inheritance. After using this, stop. Evaluate results. Then proceed."))
(defun gptel-org-tools--org-ql-select-tags (buf query)
(defun gptel-org--org-ql-select-tags (buf query)
"Return every entry tagged QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -373,15 +373,15 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
(org-ql-select
(get-buffer buf)
`(tags ,query)
:action #'gptel-org-tools--heading-body))
(gptel-org-tools--result-limit result)))
:action #'gptel-org--heading-body))
(gptel-org--result-limit result)))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-tags
:name "gptel-org-tools--org-ql-select-tags"
:function #'gptel-org--org-ql-select-tags
:name "gptel-org--org-ql-select-tags"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -391,7 +391,7 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
:category "org-ql"
:description "Returns entries tagged QUERY from BUFFER, with tag inheritance. After using this, stop. Evaluate results. Only then proceed."))
(defun gptel-org-tools--org-ql-select-rifle (buf query)
(defun gptel-org--org-ql-select-rifle (buf query)
"Return every entry matching keyword QUERY from BUFFER."
(let* ((buffer (get-buffer buf))
(mode (buffer-local-value 'major-mode buffer)))
@ -401,15 +401,15 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
(org-ql-select
buffer
`(rifle ,query)
:action #'gptel-org-tools--heading-body)))
(gptel-org-tools--result-limit result))
:action #'gptel-org--heading-body)))
(gptel-org--result-limit result))
(message "Buffer '%s' isn't an org-mode buffer." buf))
(message "Buffer '%s' does not exist." buf))))
(add-to-list 'gptel-org-tools
(add-to-list 'gptel-org
(gptel-make-tool
:function #'gptel-org-tools--org-ql-select-rifle
:name "gptel-org-tools--org-ql-select-rifle"
:function #'gptel-org--org-ql-select-rifle
:name "gptel-org--org-ql-select-rifle"
:args (list '(:name "buffer"
:type string
:description "The name of the buffer. See the NAME column in `list-buffers`. Using filename fails.")
@ -419,5 +419,5 @@ After using this, stop. Evaluate the relevance of the results. Then continue."))
:category "org-ql"
:description "Returns entries (heading and body) matching QUERY from BUFFER. This may pull too many results, only use if other tools fail. After using this, stop. Evaluate results. If necessary, re-plan. Only then proceed."))
(provide 'gptel-org-tools)
;;; gptel-org-tools.el ends here
(provide 'gptel-org)
;;; gptel-org.el ends here