summaryrefslogtreecommitdiff
path: root/gptel-tools/move_to_trash.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
committerCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
commit092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch)
treeea81999b8442246c978b364dd90e8c752af50db5 /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.el142
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