From 30e7d29bb425e63cc727562b4f7c1f97b7d5c2d2 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Thu, 30 Oct 2025 22:11:56 -0500 Subject: [PATCH 1/1] Initial add --- gptel | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 gptel diff --git a/gptel b/gptel new file mode 100644 index 0000000..b031cbd --- /dev/null +++ b/gptel @@ -0,0 +1,329 @@ +;;; -*- lisp-data -*- +(require 'gptel-integrations) +(add-hook 'gptel-post-response-functions 'gptel-end-of-response) +(load-file "~/.password-store/net/mcphubservers-emacs.gpg") + +;; (gptel-make-tool +;; :name "create_file" ; javascript-style snake_case name +;; :function (lambda (path filename content) ; the function that runs +;; (let ((full-path (expand-file-name filename path))) +;; (with-temp-buffer +;; (insert content) +;; (write-file full-path)) +;; (format "Created file %s in %s" filename path))) +;; :description "Create a new file with the specified content" +;; :args (list '(:name "path" ; a list of argument specifications +;; :type string +;; :description "The directory where to create the file") +;; '(:name "filename" +;; :type string +;; :description "The name of the file to create") +;; '(:name "content" +;; :type string +;; :description "The content to write to the file")) +;; :category "filesystem") ; An arbitrary label for grouping + +(gptel-make-tool + :name "cargo_build" + :function (lambda (workingdirectory) + (cd workingdirectory) + (let ((output (shell-command-to-string "cargo build"))) + (if (string-empty-p output) + "cargo build completed successfully (no output)" + output))) + :description "Run cargo build command and return the output" + :args (list '(:name "workingdirectory" + :type string + :description "The project's directory to change to before running the command. Defaults to '.'" + )) + :category "rust") ; Group under rust tools + +(gptel-make-tool + :name "cargo_test" + :function (lambda (workingdirectory) + (cd workingdirectory) + (let ((output (shell-command-to-string "cargo test"))) + (if (string-empty-p output) + "cargo test completed successfully (no output)" + output))) + :description "Run cargo test command and return the output" + :args (list '(:name "workingdirectory" + :type string + :description "The project's directory to change to before running the command. Defaults to '.'" + )) + :category "rust") ; Group under rust tools + +(gptel-make-tool + :name "cargo_check" + :function (lambda (workingdirectory) + (cd workingdirectory) + (let ((output (shell-command-to-string "cargo check"))) + (if (string-empty-p output) + "cargo check completed successfully (no output)" + output))) + :description "Run cargo check command and return the output" + :args (list '(:name "workingdirectory" + :type string + :description "The project's directory to change to before running the command. Defaults to '.'" + )) + :category "rust") ; Group under rust tools + +(gptel-make-tool + :name "cargo_search" + :function (lambda (searchstring) + (let ((output (shell-command-to-string (format "cargo search %s" searchstring)))) + (if (string-empty-p output) + "cargo search completed successfully (no output)" + output))) + :description "Search the cargo registry for packages" + :args (list '(:name "searchstring" + :type string + :description "The string to search for in the cargo registry" + ) + ) + :category "rust") + +;; https://github.com/skissue/llm-tool-collection +(add-to-list 'load-path "/home/erik/src/llm-tool-collection/") +(require 'llm-tool-collection) +(mapcar (apply-partially #'apply #'gptel-make-tool) + (llm-tool-collection-get-all)) + +(gptel-make-tool + :name "browse_url" + :function (lambda (url) + (browse-url url) + ) + :description "Open a browser tab with the specified URL. This is desirable to view technical documentation of known APIs or other technical specifications" + :args (list '(:name "url" + :type string + :description "The URL to open" + ) + ) + :category "www") + +(gptel-make-tool + :name "current_time" + :function (lambda () + (current-time-string) + ) + :confirm nil + :description "Returns the current time and date." + :args nil) + +(gptel-make-tool + :name "current_directory" + :function (lambda () + (pwd) + ) + :category "filesystem" + :confirm nil + :description "Returns the current working directory." + :args nil) + +(gptel-make-tool + :name "read_file_noconfirm" + :function (lambda (path) + (with-temp-buffer + (insert-file-contents (expand-file-name path)) + (buffer-string))) + :category "filesystem" + :confirm nil + :description "Read the contents of a file and return its content as a string." + :args (list '(:name "path" + :type string + :description "Path to the file. Supports relative paths and '~'" + ) + ) + ) + +(gptel-make-tool + :name "list_directory_noconfirm" + :function (lambda (path) + (let ((expanded-path (expand-file-name path))) + (if (file-directory-p expanded-path) + (string-join `(,(format "Contents of %s:" path) + ,@(directory-files expanded-path)) + "\n") + (error "%s is not a directory" expanded-path))) + ) + :category "filesystem" + :confirm nil + :description "List the contents of a specified directory." + :args (list '(:name "path" + :type string + :description "Path to the file. Supports relative paths and '~'" + ) + ) + ) + +(gptel-make-tool + :name "current_directory" + :function (lambda () + (pwd) + ) + :category "filesystem" + :confirm nil + :description "Returns the current working directory." + :args nil) + +(gptel-make-tool + :name "replace_file" + :function (lambda (workingdirectory path content) + (cd workingdirectory) + (let ((expanded-path (expand-file-name path))) + (with-temp-file expanded-path + (insert content)) + (format "File created successfully: %s" path) + ) + ) + :confirm t + :description "Replace the contents of an existing file with the specified content" + :args (list '(:name "workingdirectory" + :type string + :description "The directory to change to before running the command. Defaults to '.' but should have a Cargo.toml or ledger files" + ) + '(:name "path" + :type string + :description "Path to the file. Supports relative paths and '~'" + ) + '(:name "content" + :type string + :description "Content to write to the file" + ) + ) + :category "filesystem") + +(gptel-make-tool + :name "apply_patch" + :function (lambda (workingdirectory patch) + (cd workingdirectory) + (let ((output-buffer (generate-new-buffer "*patch-output*"))) + (call-process-region (concat patch "\n") nil "patch" nil output-buffer t "-p1" "--fuzz" "3") + (message "Patch output: %s" (with-current-buffer output-buffer (buffer-string))) + (with-current-buffer output-buffer (buffer-string)) + ) + ) + :description "Patch files in the working directory. If the patch fails to apply, generate a new patch and try again." + :args (list '(:name "workingdirectory" + :type string + :description "The project's directory to change to before running the command. Defaults to '.'" + ) + '(:name "patch" + :type string + :description "A patch in unified diff format. The file hunks are relative to workingdirectory with one leading fake directory slug (patch will strip it with -p1)." + ) + ) + :confirm t + :category "filesystem") + +(gptel-make-preset 'rustdev + :description "A preset optimized for coding tasks" + :backend "DeepSeek" + :model 'deepseek-chat + :system "You are an expert rust coding assistant. Your role is to provide high-quality code solutions, refactorings, and explanations. Alter only files that are under the specified project directory, and confirum the usage of these tools only. Read only the files under the specified project directory, or the rust library source code under ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f. Do not confirm the use of tools that only read files, list_directory, run cargo commands or browse URLs. When searching or adding libraries, choose the latest version by searching cargo. Add unit tests when it's not too difficult." + :confirm-tool-calls 'auto + :temperature 0.0 + :tools '( + "apply_patch" + "browse_url" + "cargo_build" + "cargo_check" + "cargo_search" + "cargo_test" + "create_directory" + "create_file" + "edit_buffer" + "list_directory_noconfirm" + "read_file_noconfirm" + ;; "replace_file" + "view_buffer" + ) + ) + +;; (defun match-email-files (lastndays searchstring) +;; "Find maildir files in the last n days matching the search string" +;; (interactive "sRecent days: \nsSearch string: ") +;; (let ((output-buffer (generate-new-buffer "matching-emails"))) +;; (call-process-region +;; "" nil "find" nil output-buffer nil +;; "/home/erik/var/mutt" +;; "-type" "f" +;; "-mtime" (format "-%s" lastndays) +;; "-exec" "grep" "-l" searchstring "{}" ";" +;; ) +;; (with-current-buffer output-buffer +;; (message "%s" (buffer-string)) +;; (buffer-string)) +;; ) +;; ) + +(gptel-make-tool + :name "find_matching_emails" + :function (lambda (last_n_days search_string) + (let ((output-buffer (generate-new-buffer "matching-emails"))) + (call-process-region + "" nil "find" nil output-buffer nil + "/home/erik/var/mutt" + "-type" "f" + "-mtime" (format "-%d" last_n_days) + "-exec" "grep" "-l" search_string "{}" ";" + ) + (with-current-buffer output-buffer + ;; (message "%s" (buffer-string)) + (buffer-string)) + ) + ) + :description "List file paths representing recent emails that match the given string. The output paths can be then read with read_file_noconfirm." + :confirm nil + :args (list '(:name "last_n_days" + :type number + :description "How many recent days of emails to check. Start with 1. If there are no results, try again with increased values up to 7" + ) + '(:name "search_string" + :type string + :description "The string to search for in the email headers and body" + ) + ) + :category "mail") + +(gptel-make-tool + :name "hledger_check" + :function (lambda (ledgerfile) + (let ((output (shell-command-to-string (format "hledger -f %s check" ledgerfile)))) + (if (string-empty-p output) + "hledger check completed successfully (no output)" + output))) + :description "Ensure the ledger files balance correctly. If there is error output it describes how much the error is off." + :confirm t + :args (list '(:name "ledgerfile" + :type string + :description "The base ledger file" + ) + ) + :category "ledger") + +(gptel-make-preset 'ledger + :description "A preset for hledger tasks" + :backend "DeepSeek" + :model 'deepseek-chat + :system "You add valid account data to hledger accounting files by applying a patch. The working diretory is /home/erik/src/ledgerfile and you may only list files under that directory. The main ledgerfile is /home/erik/src/ledgerfile/2025only. You may read only the ledgerfile or files included by the ledgerfile. You may write only to the month file (like 202510 or 202512). A credit card purchase is added by adding the transaction as a credit (usually to Expenses:Dining/Entertainment) and a corresponding (blank) debit to Liabilities:Chase; also the same amount is credited to the following 'Chase balance' transaction (which may be in next month's ledgerfile). Remember to actually patch the file. A patch should only have one hunk; if multiple hunks are required, generate multiple patches. After adding a transaction, check that the ledger is balanced and fix any errors." + :confirm-tool-calls 'auto + :temperature 0.0 + :tools '( + "apply_patch" + "current_directory" + "current_time" + "find_matching_emails" + "hledger_check" + "list_directory_noconfirm" + "read_file_noconfirm" + ;; "add_time" + ;; "compare_time" + ;; "convert_timezone" + ;; "edit_buffer" + ;; "relative_time" + ;; "replace_file" + ;; "view_buffer" + ) + ) -- 2.52.0