diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-23 20:12:58 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-23 20:12:58 -0400 |
| commit | e41c25068d0cec9434895a6d3e3a25d3a26f645f (patch) | |
| tree | 5e30938a3fd6d80f501ffe3e6c1c187c5ddeb2c9 /custom | |
| parent | a936e081b7270fbd4f1e7e9cb67ca1d4c2291ce6 (diff) | |
| download | dotemacs-e41c25068d0cec9434895a6d3e3a25d3a26f645f.tar.gz dotemacs-e41c25068d0cec9434895a6d3e3a25d3a26f645f.zip | |
chore(ai): archive gptel and remove it from the live config
I archived gptel to archive/gptel/ since I rarely use it. Moved there: the six gptel modules (ai-config, ai-conversations, ai-conversations-browser, ai-mcp, ai-quick-ask, ai-rewrite), the gptel-tools/ directory, custom/gptel-prompts.el, their test files and utilities, and the four gptel-only specs.
Scrubbed from the live config: the ai-config require in init.el, which also drops the whole C-; a keymap; the gptel-mode emojify hook in font-config.el; the gptel-tools entries in the Makefile clean target and the coverage runner; and the gptel feature notes in README. Cancelled the open gptel tasks in todo.org (the AI Open Work issues, the feature-extension brainstorm, the velox gptel-magit bug).
ai-term stays. It is the ghostel Claude launcher, independent of gptel.
Verified: every module loads, a batch init launch reaches completion clean, and the full test suite shows only pre-existing coverage failures unrelated to this change.
Diffstat (limited to 'custom')
| -rw-r--r-- | custom/gptel-prompts.el | 418 |
1 files changed, 0 insertions, 418 deletions
diff --git a/custom/gptel-prompts.el b/custom/gptel-prompts.el deleted file mode 100644 index a2b266f27..000000000 --- a/custom/gptel-prompts.el +++ /dev/null @@ -1,418 +0,0 @@ -;;; gptel-prompts.el --- GPTel directive management using files -*- lexical-binding: t -*- - -;; Copyright (C) 2025 John Wiegley - -;; Author: John Wiegley <johnw@gnu.org> -;; Created: 19 May 2025 -;; Version: 1.0 -;; Keywords: ai gptel prompts -;; X-URL: https://github.com/jwiegley/dot-emacs -;; Package-Requires: ((emacs "24.1")) - -;; This file is NOT part of GNU Emacs. - -;;; License: - -;; This program is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License as -;; published by the Free Software Foundation; either version 2, or (at -;; your option) any later version. - -;; This program is distributed in the hope that it will be useful, but -;; WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;; General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. - -;;; Commentary: - -;; This package provides enhanced prompt management capabilities for GPTel, -;; allowing you to organize and dynamically load AI prompts from external -;; files rather than hardcoding them in your Emacs configuration. - -;; Key Features: -;; -;; * Multi-format prompt support: Load prompts from .txt, .md, .org, .json, -;; .eld (Emacs Lisp data), .el (Emacs Lisp functions), and .poet/.jinja -;; (Prompt Poet/Jinja2 templates) -;; -;; * Template interpolation: Use Jinja2-style {{variable}} syntax with -;; customizable variables and dynamic functions -;; -;; * File watching: Automatically reload prompts when files change -;; -;; * Project-aware prompts: Automatically load project-specific conventions -;; from CONVENTIONS.md or CLAUDE.md files -;; -;; * Conversation format support: Handle multi-turn conversations with -;; system/user/assistant roles - -;; Setup: -;; -;; (use-package gptel-prompts -;; :after (gptel) -;; :custom -;; (gptel-prompts-directory "~/my-prompts") -;; :config -;; (gptel-prompts-update) -;; ;; Optional: auto-reload on file changes -;; (gptel-prompts-add-update-watchers)) - -;; File Formats: -;; -;; * Plain text (.txt, .md, .org): Used as-is for system prompts -;; * JSON (.json): Array of {role: "system/user/assistant", content: "..."} -;; * Emacs Lisp data (.eld): List format for conversations -;; * Emacs Lisp code (.el): Lambda functions for dynamic prompts -;; * Prompt Poet (.poet, .j2, .jinja, .jinja2): YAML + Jinja2 templates - -;; Template Variables: -;; -;; Use {{variable_name}} in your prompts. Variables can be defined in -;; `gptel-prompts-template-variables' or generated dynamically by functions -;; in `gptel-prompts-template-functions'. - -;; Project Integration: -;; -;; Add `gptel-prompts-project-conventions' to `gptel-directives' to -;; automatically load project-specific prompts from CONVENTIONS.md or -;; CLAUDE.md files in your project root. - -;;; Code: - -(require 'cl-lib) -(require 'cl-macs) -(require 'rx) -(require 'filenotify) -(require 'gptel) - -(defgroup gptel-prompts nil - "Helper library for managing GPTel prompts (aka directives)." - :group 'gptel) - -(defcustom gptel-prompts-directory "~/.emacs.d/prompts" - "*Directory where GPTel prompts are defined, one per file. - -Note that files can be of different types, which will cause them to be -represented as directives differently. See `gptel-prompts-file-regexp' -for more information." - :type 'file - :group 'gptel-prompts) - -(defcustom gptel-prompts-file-regexp - (rx "." (group - (or "txt" - "md" - "org" - "eld" - "el" - (seq "j" (optional "inja") (optional "2")) - "poet" - "json")) - string-end) - "*Directory where GPTel prompts are defined, one per file. - -Note that files can be of different types, which will cause them -to be represented as directives differently: - - .txt, .md, .org Purely textual prompts that are used as-is - .eld Must be a Lisp list represent a conversation: - SYSTEM, USER, ASSISTANT, [USER, ASSISTANT, ...] - .el Must evaluate to a Lisp function - .poet See https://github.com/character-ai/prompt-poet - .json JSON list of role-assigned prompts" - :type 'regexp - :group 'gptel-prompts) - -(defcustom gptel-prompts-template-variables nil - "*An alist of names to strings used during template expansion. - -Example: - ((\"name\" . \"John\") - (\"hobbies\" . \"Emacs\")) - -These would referred to using {{ name }} and {{ hobbies }} in the -prompt template." - :type '(alist :key-type string :value-type string) - :group 'gptel-prompts) - -(defcustom gptel-prompts-template-functions - '(gptel-prompts-add-current-time) - "*Set of functions run when a template prompt is used. - -These are called when the template is going to be used by -`gptel-request'. Each function receives the name of the template file, -and must return either nil or an alist of variable values to prepend to -`gptel-prompts-template-variables'. See that variable's documentation -for the expected format." - :type '(list function) - :group 'gptel-prompts) - -(defun gptel-prompts-process-prompts (prompts) - "Convert from a list of PROMPTS in dialog format, to GPTel. - -For example: - - (((role . \"system\") - (content . \"Sample\") - (name . \"system instructions\")) - ((role . \"system\") - (content . \"Sample\") - (name . \"further system instructions\")) - ((role . \"user\") - (content . \"Sample\") - (name . \"User message\")) - ((role . \"assistant\") - (content . \"Sample\") - (name . \"Model response\")) - ((role . \"user\") - (content . \"Sample\") - (name . \"Second user message\"))) - -Becomes: - - (\"system instructions\nfurther system instructions\" - (prompt \"User message\") - (response \"Model response\") - (prompt \"Second user message\"))" - (let ((system "") result) - (dolist (prompt prompts) - (let ((content (alist-get 'content prompt)) - (role (alist-get 'role prompt))) - (cond - ((string= role "system") - (setq system (if (string-empty-p system) - content - (concat system "\n" content)))) - ((string= role "user") - (setq result (cons (list 'prompt content) result))) - ((string= role "assistant") - (setq result (cons (list 'response content) result))) - ((string= role "tool") - (error "Tools not yet supported in Poet prompts")) - (t - (error "Role not recognized in prompt: %s" - (pp-to-string prompt)))))) - (cons system (nreverse result)))) - -(defun gptel-prompts-interpolate (prompt &optional file) - "Expand Jinja-style references to `gptel-prompts-template-variables'. -The references are expected in the string PROMPT, possibly from FILE. -`gptel-prompts-template-functions' are called to add to this list as -well, so some variables can be dynamic in nature." - (require 'templatel) - (let ((vars (apply #'append - (mapcar #'(lambda (f) (funcall f file)) - gptel-prompts-template-functions)))) - (templatel-render-string - prompt - (cl-remove-duplicates - (append vars gptel-prompts-template-variables) - :test #'string= :from-end t :key #'car)))) - -(defun gptel-prompts-interpolate-buffer () - "Expand Jinja-style references to `gptel-prompts-template-variables'. -See `gptel-prompts-interpolate'. -This function can be added to `gptel-prompt-transform-functions'." - (let ((replacement (gptel-prompts-interpolate (buffer-string)))) - (delete-region (point-min) (point-max)) - (insert replacement))) - -(defun gptel-prompts-poet (file) - "Read Yaml + Jinja FILE in prompt-poet format." - (require 'yaml) - (gptel-prompts-process-prompts - (mapcar #'yaml--hash-table-to-alist - (yaml-parse-string - (gptel-prompts-interpolate - (with-temp-buffer - (insert-file-contents file) - (buffer-string)) - file))))) - -(defun gptel-prompts-process-file (file) - "Process FILE and return appropriate content. - -FILE is a string path to the file to be processed. - -Handles different file types based on extension: -- .eld files: Read as Emacs Lisp data, must evaluate to a list -- .el files: Read as Emacs Lisp code, must evaluate to a function/lambda -- .json files: Parse as JSON array and process as prompts via - `gptel-prompts-process-prompts' -- .j2/.jinja/.jinja2/.poet files: Return lambda that calls - `gptel-prompts-poet' with FILE -- Other files: Return trimmed file contents as plain text string - -Returns the processed content in the appropriate format for each file -type. Signals an error if the file content doesn't match expected format -for typed files." - (cond ((string-match "\\.eld\\'" file) - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (let ((lst (read (current-buffer)))) - (if (listp lst) - lst - (error "Emacs Lisp data prompts must evaluate to a list"))))) - ((string-match "\\.el\\'" file) - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (let ((func (read (current-buffer)))) - (if (and (functionp func) - (listp func) - (eq 'lambda (car func))) - func - (error "Emacs Lisp prompts must evaluate to a function/lambda"))))) - ((string-match "\\.json\\'" file) - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (let ((conversation (json-read))) - (if (vectorp conversation) - (gptel-prompts-process-prompts (seq-into conversation 'list)) - (error "Emacs Lisp prompts must evaluate to a list"))))) - ((string-match "\\.\\(j\\(inja\\)?2?\\|poet\\)\\'" file) - `(lambda () (gptel-prompts-poet ,file))) - (t - (with-temp-buffer - (insert-file-contents file) - (string-trim (buffer-string)))))) - -(defun gptel-prompts-read-directory (dir) - "Read prompts from directory DIR and establish them in `gptel-directives'." - (cl-loop for file in (directory-files dir t gptel-prompts-file-regexp) - collect (cons (intern (file-name-sans-extension - (file-name-nondirectory file))) - (gptel-prompts-process-file file)))) - -(defun gptel-prompts-update () - "Update `gptel-directives' from files in `gptel-prompts-directory'." - (interactive) - (dolist (prompt (gptel-prompts-read-directory gptel-prompts-directory)) - (setq gptel-directives - (cl-delete-if #'(lambda (x) (eq (car x) (car prompt))) - gptel-directives)) - (add-to-list 'gptel-directives prompt))) - -(defun gptel-prompts-add-current-time (_file) - "Add the current time as a variable for Poet interpolation." - `(("current_time" . ,(format-time-string "%F %T")))) - -(defun gptel-prompts-add-update-watchers () - "Watch all files in DIR and run CALLBACK when any is modified." - (let ((watches (list (file-notify-add-watch - gptel-prompts-directory '(change) - #'(lambda (&rest _events) - (gptel-prompts-update)))))) - (dolist (file (directory-files gptel-prompts-directory - t gptel-prompts-file-regexp)) - (when (file-regular-p file) - (push (file-notify-add-watch file '(change) - #'(lambda (&rest _events) - (gptel-prompts-update))) - watches))) - watches)) - -(defvar gptel-prompts--project-conventions-alist nil - "Alist mapping projects to project conventions for LLMs.") - -(defcustom gptel-prompts-project-files - '("CONVENTIONS.md" - "CLAUDE.md" - "AGENTS.md" - (".github" . "copilot-instructions\\.md") - (".instructions.d" . "^.*\\.md$") - ".instructions.md") - "A list of files or directories with prompts for the current project. -Entries can be strings (file/directory names) or cons cells where the -CAR is a directory path and the CDR is either a regexp string or a -filter function for selecting which files in that directory should be -chosen. - -The first matching rule in the list for a given project is used, with -the rest ignored. - -If a directory is specified without a filter (as a plain string), all -markdown files within it will be aggregated into a single prompt." - :type '(repeat (choice file directory - (cons directory (choice regexp function)))) - :group 'gptel-prompts) - -(defun gptel-prompts--read-directory-filtered (dir regexp-or-function) - "Read files from DIR for which REGEXP-OR-FUNCTION is a match." - (when (and (file-directory-p dir) - (file-readable-p dir)) - (let ((files - (cl-remove-if-not - (cond - ((functionp regexp-or-function) - (lambda (f) - (funcall regexp-or-function (file-name-nondirectory f)))) - ((stringp regexp-or-function) - (lambda (f) - (string-match-p regexp-or-function (file-name-nondirectory f)))) - (t (error "Invalid filter: %s" regexp-or-function))) - (directory-files dir t "^[^.].*" t)))) - (unless (null files) - (mapconcat - (lambda (file) - (when (and (file-regular-p file) - (file-readable-p file)) - (with-temp-buffer - (insert-file-contents file) - (buffer-string)))) - files "\n\n"))))) - -(defun gptel-prompts--read-directory (dir) - "Read all Markdown files from DIR, concated together." - (let ((contents - (mapconcat - (lambda (file) - (when (and (file-regular-p file) - (file-readable-p file)) - (with-temp-buffer - (insert-file-contents file) - (buffer-string)))) - (directory-files dir t "^[^.].*\\.md$" t) - "\n\n"))) - (unless (string-empty-p contents) - contents))) - -(defun gptel-prompts-project-conventions () - "System prompt is obtained from project CONVENTIONS. -This function should be added to `gptel-directives'. To replace -the default directive, use: - - (setf (alist-get \\'default gptel-directives) - #\\'gptel-project-conventions)" - (when-let* ((project (project-current)) - (root (project-root project))) - (with-memoization - (alist-get root gptel-prompts--project-conventions-alist - nil nil #'equal) - (or (cl-loop - for item in gptel-prompts-project-files - for path = (expand-file-name - (if (consp item) (car item) item) - root) - when (file-readable-p path) - return (cond - ((consp item) - (gptel-prompts--read-directory-filtered (car item) (cdr item))) - ((file-directory-p path) - (gptel-prompts--read-directory path)) - (t - (with-temp-buffer - (insert-file-contents path) - (buffer-string))))) - "You are a helpful assistant. Respond concisely.")))) - -(provide 'gptel-prompts) - -;;; gptel-prompts.el ends here |
