diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /gptel-tools/move_to_trash.el | |
changing repositories
Diffstat (limited to 'gptel-tools/move_to_trash.el')
| -rw-r--r-- | gptel-tools/move_to_trash.el | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/gptel-tools/move_to_trash.el b/gptel-tools/move_to_trash.el new file mode 100644 index 00000000..6ea97995 --- /dev/null +++ b/gptel-tools/move_to_trash.el @@ -0,0 +1,142 @@ +;;; move_to_trash.el --- Move files/directories to trash for gptel -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 + +;; Author: gptel-tool-writer +;; Keywords: convenience, tools, files + +;; This file is not part of GNU Emacs. + +;; 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 3 of the License, 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. + +;;; Commentary: + +;; This file provides a gptel tool for moving files and directories to the trash. +;; Files are moved to ~/.local/share/Trash/files with automatic timestamping for +;; name conflicts. The tool operates only within the home directory and /tmp +;; for security reasons. + +;;; Code: + +(require 'gptel) +(require 'subr-x) + +(defun gptel--move-to-trash-generate-unique-name (original-name trash-dir) + "Generate a unique name for ORIGINAL-NAME in TRASH-DIR. +If a file with the same name exists, append a timestamp in the format +YYYY-MM-DD-HH-MM-SS." + (let* ((base-name (file-name-nondirectory original-name)) + (target-path (expand-file-name base-name trash-dir))) + (if (not (file-exists-p target-path)) + target-path + ;; Name conflict: add timestamp + (let* ((extension (file-name-extension base-name t)) + (name-sans-ext (file-name-sans-extension base-name)) + (timestamp (format-time-string "%Y-%m-%d-%H-%M-%S")) + (new-name (if extension + (concat name-sans-ext "-" timestamp extension) + (concat base-name "-" timestamp)))) + (expand-file-name new-name trash-dir))))) + +(defun gptel--move-to-trash-validate-path (path) + "Validate that PATH is safe to trash. +Returns the expanded path if valid, signals an error otherwise. +Ensures path is within home directory or /tmp, and prevents +trashing of critical system directories." + (let ((expanded-path (expand-file-name path)) + (home-dir (expand-file-name "~")) + (critical-dirs (list (expand-file-name "~") + (expand-file-name "~/.emacs.d") + (expand-file-name "~/.config") + "/tmp"))) + ;; Security check: must be within allowed directories + (unless (or (string-prefix-p home-dir expanded-path) + (string-prefix-p "/tmp" expanded-path)) + (error "Path must be within home directory or /tmp: %s" path)) + + ;; Prevent trashing critical directories + (when (member expanded-path critical-dirs) + (error "Cannot trash critical directory: %s" path)) + + ;; Existence check + (unless (file-exists-p expanded-path) + (error "File or directory does not exist: %s" path)) + + expanded-path)) + +(defun gptel--move-to-trash-perform (expanded-path trash-dir) + "Move EXPANDED-PATH to TRASH-DIR with unique naming. +Returns a formatted message describing the operation." + (let* ((is-directory (file-directory-p expanded-path)) + (is-symlink (file-symlink-p expanded-path)) + (trash-path (gptel--move-to-trash-generate-unique-name + expanded-path trash-dir)) + (item-type (cond + (is-symlink "Symlink") + (is-directory "Directory") + (t "File")))) + + ;; Perform the move + (condition-case move-err + (progn + (rename-file expanded-path trash-path) + + ;; Verify success + (cond + ((file-exists-p expanded-path) + (error "Failed to move %s to trash - file still exists at original location" + expanded-path)) + ((not (file-exists-p trash-path)) + (error "Move operation failed - file not found in trash")) + (t + (format "%s moved to trash: %s → %s" + item-type + (abbreviate-file-name expanded-path) + (file-name-nondirectory trash-path))))) + (permission-denied + (error "Permission denied: cannot move %s to trash" expanded-path)) + (error + (error "Failed to move %s to trash: %s" + expanded-path (error-message-string move-err)))))) + +;; Main tool definition +(with-eval-after-load 'gptel + (gptel-make-tool + :name "move_to_trash" + :function (lambda (path) + "Move PATH to the trash directory. +Creates the trash directory if needed, handles naming conflicts, +and provides detailed error messages." + (condition-case err + (let* ((trash-dir (expand-file-name "~/.local/share/Trash/files")) + (expanded-path (gptel--move-to-trash-validate-path path))) + + ;; Ensure trash directory exists + (unless (file-exists-p trash-dir) + (make-directory trash-dir t)) + + ;; Move and return status message + (gptel--move-to-trash-perform expanded-path trash-dir)) + (error + (error "Tool error: %s" (error-message-string err))))) + :description "Move a file or directory to the trash (~/.local/share/Trash/files). Works recursively for directories. Handles name conflicts with timestamps. Operates only within home directory and /tmp. Does not follow symlinks. Synonyms: delete, remove, trash file/directory." + :args (list '(:name "path" + :type string + :description "Path to the file or directory to move to trash. Must be within home directory or /tmp.")) + :category "filesystem" + :confirm nil ; No confirmation needed + :include t)) + +;; Automatically add to gptel-tools on load +(add-to-list 'gptel-tools (gptel-get-tool '("filesystem" "move_to_trash"))) + +(provide 'move_to_trash) +;;; move_to_trash.el ends here |
