]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update dwim-shell-command
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 22 Dec 2024 11:45:23 +0000 (12:45 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 22 Dec 2024 11:45:45 +0000 (12:45 +0100)
Signed-off-by: Andreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
elpa/dwim-shell-command-20230709.2129/dwim-shell-command-autoloads.el [deleted file]
elpa/dwim-shell-command-20230709.2129/dwim-shell-command-pkg.el [deleted file]
elpa/dwim-shell-command-20230709.2129/dwim-shell-command.el [deleted file]
elpa/dwim-shell-command-20230709.2129/dwim-shell-commands.el [deleted file]
elpa/dwim-shell-command-20241115.845/dwim-shell-command-autoloads.el [new file with mode: 0644]
elpa/dwim-shell-command-20241115.845/dwim-shell-command-pkg.el [new file with mode: 0644]
elpa/dwim-shell-command-20241115.845/dwim-shell-command.el [new file with mode: 0644]
elpa/dwim-shell-command-20241115.845/dwim-shell-commands.el [new file with mode: 0644]

diff --git a/elpa/dwim-shell-command-20230709.2129/dwim-shell-command-autoloads.el b/elpa/dwim-shell-command-20230709.2129/dwim-shell-command-autoloads.el
deleted file mode 100644 (file)
index 12956bb..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-;;; dwim-shell-command-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
-                         (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "dwim-shell-command" "dwim-shell-command.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from dwim-shell-command.el
-
-(register-definition-prefixes "dwim-shell-command" '("dwim-shell-command"))
-
-;;;***
-\f
-;;;### (autoloads nil "dwim-shell-commands" "dwim-shell-commands.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from dwim-shell-commands.el
-
-(register-definition-prefixes "dwim-shell-commands" '("dwim-shell-commands-"))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("dwim-shell-command-pkg.el") (0 0 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; dwim-shell-command-autoloads.el ends here
diff --git a/elpa/dwim-shell-command-20230709.2129/dwim-shell-command-pkg.el b/elpa/dwim-shell-command-20230709.2129/dwim-shell-command-pkg.el
deleted file mode 100644 (file)
index 49355d5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-(define-package "dwim-shell-command" "20230709.2129" "Shell commands with DWIM behaviour"
-  '((emacs "28.1"))
-  :commit "2503c5a749eaa288d5e7250561893416fd40fbf9" :authors
-  '(("Alvaro Ramirez"))
-  :maintainers
-  '(("Alvaro Ramirez"))
-  :maintainer
-  '("Alvaro Ramirez")
-  :url "https://github.com/xenodium/dwim-shell-command")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
diff --git a/elpa/dwim-shell-command-20230709.2129/dwim-shell-command.el b/elpa/dwim-shell-command-20230709.2129/dwim-shell-command.el
deleted file mode 100644 (file)
index c986404..0000000
+++ /dev/null
@@ -1,1037 +0,0 @@
-;;; dwim-shell-command.el --- Shell commands with DWIM behaviour -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2022 Alvaro Ramirez https://xenodium.com
-
-;; Author: Alvaro Ramirez
-;; Package-Requires: ((emacs "28.1"))
-;; URL: https://github.com/xenodium/dwim-shell-command
-;; Version: 0.48
-
-;; This package 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, or (at your option)
-;; any later version.
-
-;; This package 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.  If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Provides `dwim-shell-command' as an opinionated DWIM alternative to
-;; `shell-command'.
-;;
-;; Use `dwim-shell-command-on-marked-files' to create your own command
-;; line utilities, invoked via M-x.
-;;
-;; See examples at https://github.com/xenodium/dwim-shell-command/blob/main/dwim-shell-commands.el
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'comint)
-(require 'dired)
-(require 'dired-aux)
-(require 'map)
-(require 'seq)
-(require 'shell)
-(require 'simple)
-(require 'subr-x)
-(require 'view)
-
-(defcustom dwim-shell-command-prompt
-  "DWIM shell command (<<f>> <<fne>> <<e>>): "
-  "`dwim-shell-command' prompt.  Modify if shorter is preferred."
-  :type 'string
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-default-command
-  " '<<f>>'"
-  "Set to nil if no default shell command wanted."
-  :type 'string
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-buffer-name
-  "DWIM shell command"
-  "`dwim-shell-command' buffer name.  Modify if shorter is preferred."
-  :type 'string
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-prompt-on-error nil
-  "If t, prompt user to focus buffer on process error.
-Otherwise, automatically focus buffer on process error."
-  :type 'boolean
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-shell-util nil
-  "Shell util, for example: \"zsh\" or \"bash\".
-Set to nil to use `shell-file-name'."
-  :type 'string
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-shell-args nil
-  "Shell util, for example: '(\"-x\" \"-c\").
-Set to nil to use `shell-command-switch'."
-  :type '(repeat string)
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-shell-trace nil
-  "Attempt to add --xtrace to shell command to debug."
-  :type 'boolean
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-done-buffer-name
-  (lambda (name)
-    (format "✅ %s %s" name (propertize "done" 'face 'success)))
-  "Function to format buffer name on success.
-Use `identify' to remove formatting."
-  :type 'function
-  :group 'dwim-shell-command)
-
-(defcustom dwim-shell-command-error-buffer-name
-  (lambda (name)
-    (format "⛔️ %s %s" name (propertize "error" 'face 'error)))
-  "Function to format buffer name on error.
-Use `identify' to remove formatting."
-  :type 'function
-  :group 'dwim-shell-command)
-
-(defvar dwim-shell-command--commands nil "All commands in progress.")
-
-(cl-defstruct
-    dwim-shell-command--command
-  "Describes a command in progress."
-  script
-  process
-  name
-  calling-buffer
-  reporter
-  on-completion
-  files-before
-  silent-success
-  error-autofocus
-  monitor-directory)
-
-(defun dwim-shell-command (prefix)
-  "Execute DWIM shell command asynchronously using noweb templates.
-
-Which files
-
-  `dwim-shell-command' attempts to guess which file(s) you may want
-  the command to operate on.
-
-  1. If visiting a `dired' buffer, draw the marked file(s).
-  2. If visiting any other buffer with an associated file, use that.
-
-Templates
-
-  Operate on drawn files using either the following:
-
-    <<f>> (file path)
-    <<fne>> (file path without extension)
-    <<e>> (extension)
-    <<td>> (generate a temporary directory)
-    <<*>> (all files joined)
-    <<cb>> (clipboard)
-    <<n>>, <<1n>>, or <<An>> (for current iteration)
-
-  For example:
-
-    With drawn files '(\"path/to/image1.png\" \"path/to/image2.png\")
-
-   \"convert '<<f>>' '<<fne>>.jpg'\" expands to
-
-     \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
-     \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
-
-   while \"ls -lh <<*>>\" expands to
-
-     \"ls -lh path/to/image1.png path/to/image2.png\"
-
-Focus
-
-  `dwim-shell-command' creates a process buffer to capture command
-  output, but doesn't display or focus on it by default.  Instead,
-  it tries to guess what's more convenient to focus on.
-
-  While the process is busy, show a spinner in the minibuffer.  No
-  focus changes.
-
-  After process is finished:
-
-  1. If there were any files created in the `default-directory',
-  jump to a `dired' buffer and move point to the new file (via
-  `dired-jump').
-
-  2. If no new files were created, automatically switch focus to the
-  process buffer and display its output.
-
-    Note: You can prevent this automatic focus by prepending your
-    command with whitespace.
-
-      |
-      V
-    \" convert '<<f>>' '<<fne>>.jpg'\"
-
-  3. If the shell command caused any errors, offer to focus the
-  process buffer and display its output.
-
-Quick exit
-
-  Process buffers are read-only and can be quickly closed by
-  pressing `q'.
-
-Prefix
-
-  With PREFIX, execute command that number of times."
-  (interactive "p")
-  (let ((script (dwim-shell-command--read-shell-command)))
-    (dwim-shell-command-on-marked-files
-     dwim-shell-command-buffer-name script
-     :repeat prefix
-     :shell-util dwim-shell-command-shell-util
-     :shell-args dwim-shell-command-shell-args
-     :silent-success (string-prefix-p " " script)
-     :error-autofocus (not dwim-shell-command-prompt-on-error))))
-
-(defun dwim-shell-command--read-shell-command ()
-  "Read a shell command from the minibuffer, using `shell-command-history'."
-  (minibuffer-with-setup-hook
-      (lambda ()
-        (beginning-of-line)
-        (setq-local minibuffer-default-add-function
-                    #'minibuffer-default-add-shell-commands))
-    (read-from-minibuffer dwim-shell-command-prompt dwim-shell-command-default-command nil nil 'shell-command-history)))
-
-(cl-defun dwim-shell-command-on-marked-files (buffer-name script &key utils extensions shell-util shell-args shell-trace shell-pipe post-process-template on-completion repeat silent-success no-progress error-autofocus monitor-directory focus-now join-separator)
-  "Create DWIM utilities executing templated SCRIPT on given files.
-
-Here's a simple utility invoking SCRIPT to convert image files to jpg.
-
-  (defun dwim-shell-command-convert-image-to-jpg ()
-    \"Convert all marked images to jpg(s).\"
-    (interactive)
-    (dwim-shell-command-on-marked-files
-     \"Convert to jpg\"
-     \"convert -verbose '<<f>>' '<<fne>>.jpg'\"
-     :utils \"convert\"))
-
-Check `dwim-shell-command-commands.el' for more examples.
-
-All command process output is written to a buffer with BUFFER-NAME.
-
-All params explained in `dwim-shell-command-execute-script'.
-
-Which files
-
-  `dwim-shell-command-on-marked-files' attempts to guess which file(s)
-  you may want the command to operate on.
-
-  1. If visiting a `dired' buffer, draw the marked file(s).
-  2. If visiting any other buffer with an associated file, use that.
-
-Templates
-
-  Operate on drawn files using either the following:
-
-    <<f>> (file path)
-    <<fne>> (file path without extension)
-    <<e>> (extension)
-    <<td>> (generate a temporary directory)
-    <<*>> (all files joined)
-    <<cb>> (clipboard)
-    <<n>>, <<1n>>, or <<An>> (for current iteration)
-
-  For example:
-
-    With drawn files '(\"path/to/image1.png\" \"path/to/image2.png\")
-
-   \"convert '<<f>>' '<<fne>>.jpg'\" expands to
-
-     \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
-     \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
-
-   while \"ls -lh <<*>>\" expands to
-
-     \"ls -lh path/to/image1.png path/to/image2.png\"
-
-Focus
-
-  `dwim-shell-command-on-marked-files' creates a process buffer to
-  capture command output, but doesn't display or focus on it by
-  default.  Instead, it tries to guess what's more convenient to focus
-  on.
-
-  While the process is busy, show a spinner in the minibuffer.  No
-  focus changes.
-
-  After process is finished:
-
-  1. If there were any files created in the `default-directory',
-  jump to a `dired' buffer and move point to the new file (via
-  `dired-jump').
-
-  2. If no new files were created, automatically switch focus to the
-  process buffer and display its output.
-
-    Note: You can prevent this automatic focus by prepending your
-    command with whitespace.
-
-      |
-      V
-    \" convert '<<f>>' '<<fne>>.jpg'\"
-
-  3. If the shell command caused any errors, offer to focus the
-  process buffer and display its output.
-
-Quick exit
-
-  Process buffers are read-only and can be quickly closed by
-  pressing `q'."
-  (dwim-shell-command-execute-script buffer-name script
-                                     :files (dwim-shell-command--files)
-                                     :utils utils
-                                     :extensions extensions
-                                     :shell-util shell-util
-                                     :shell-args shell-args
-                                     :shell-trace shell-trace
-                                     :shell-pipe shell-pipe
-                                     :post-process-template post-process-template
-                                     :on-completion on-completion
-                                     :silent-success silent-success
-                                     :no-progress no-progress
-                                     :repeat repeat
-                                     :error-autofocus error-autofocus
-                                     :monitor-directory monitor-directory
-                                     :focus-now focus-now
-                                     :join-separator join-separator))
-
-(cl-defun dwim-shell-command-execute-script (buffer-name script &key files extensions shell-util shell-args shell-trace shell-pipe utils post-process-template on-completion silent-success gen-temp-dir repeat no-progress error-autofocus monitor-directory focus-now join-separator)
-  "Execute a script asynchronously, DWIM style with SCRIPT and BUFFER-NAME.
-
-:FILES are used to instantiate SCRIPT as a noweb template.
-
-  The following are supported:
-
-    <<f>> (file path)
-    <<fne>> (file path without extension)
-    <<e>> (extension)
-    <<td>> (generate a temporary directory)
-    <<*>> (all files joined)
-    <<cb>> (clipboard)
-    <<n>>, <<1n>>, or <<An>> (for current iteration)
-
-  For example:
-
-    Given :FILES '(\"path/to/image1.png\" \"path/to/image2.png\")
-
-    \"convert '<<f>>' '<<fne>>.jpg'\" expands to
-
-      \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
-      \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
-
-    and \"ls -lh <<*>>\" expands to
-
-      \"ls -lh path/to/image1.png path/to/image2.png\"
-
-:EXTENSIONS ensures that all files in :FILES have the given
-extensions.  Can be either single string \"png\" or a list '(\"png\" \"jpg\").
-
-:SHELL-UTIL and :SHELL-ARGS can be used to specify SCRIPT interpreter.
-
-  For python, use:
-
-    (dwim-shell-command-execute-script
-       \"Print Pi\"
-       \"import math
-         print math.pi\"
-       :shell-util \"python\"
-       :shell-args \"-c\")
-
-:SHELL-PIPE can be used to pipe SCRIPT to it
-
-  For swift, use:
-
-    (dwim-shell-command-on-marked-files
-       \"Print Pi\"
-       \"print(Double.pi)\"
-       :shell-pipe \"swift -\")
-
-:UTILS ensures that all needed command line utilities are installed.
-Can be either a single string \"ffmpeg\" or a list '(\"ffmpet\" \"convert\").
-
-:POST-PROCESS-TEMPLATE enables processing template further after noweb
-instantiation.
-
-:ON-COMPLETION is invoked after SCRIPT executes (disabling DWIM
-internal behavior).
-
-:SILENT-SUCCESS to avoid jumping to process buffer if neither error
- nor file generated.
-
-:GEN-TEMP-DIR to generate a temporary directory for this command.
-This is implied when <<td>> appears in the script.
-
-:REPEAT Use to repeat script N number of times.
-
-:NO-PROGRESS Suppress progress reporting.
-
-:ERROR-AUTOFOCUS Automatically focus process buffer on error.
-
-:MONITOR-DIRECTORY Monitor this directory for new files.
-
-:FOCUS-NOW Immediately focus process buffer once started."
-  (cl-assert buffer-name nil "Script must have a buffer name")
-  (cl-assert (not (string-empty-p script)) nil "Script must not be empty")
-  (when (stringp extensions)
-    (setq extensions (list extensions)))
-  (when (and shell-util (stringp shell-util))
-    (setq shell-util (list shell-util)))
-  (setq shell-util (or shell-util
-                       (when shell-file-name
-                         (list shell-file-name))
-                       '("zsh")))
-  (setq shell-args (or shell-args
-                       (when shell-command-switch
-                         (list shell-command-switch))
-                       '("-x" "-c")))
-  (when (and shell-args (stringp shell-args))
-    (setq shell-args (list shell-args)))
-  ;; See if -x can be prepended.
-  (when (and (not (seq-contains-p shell-args "-x"))
-             (or shell-trace dwim-shell-command-shell-trace)
-             (apply #'dwim-shell-command--program-test
-                    (seq-concatenate
-                     'list shell-util '("-x") shell-args (list "echo"))))
-    (setq shell-args (seq-concatenate 'list '("-x") shell-args)))
-  (when (stringp utils)
-    (setq utils (list utils)))
-  (when (or gen-temp-dir (string-match-p "\<\<td\>\>" script 0))
-    (setq gen-temp-dir (make-temp-file "dwim-shell-command-" t)))
-  (when (and repeat (> repeat 1))
-    (cl-assert (<= (length files) 1) nil
-               "Must not repeat when multiple files are selected.")
-    (setq files (make-list repeat (or (seq-first files) "_no_file_selected_"))))
-  (when (seq-empty-p files)
-    (cl-assert (not (or (dwim-shell-command--contains-multi-file-ref script)
-                        (dwim-shell-command--contains-single-file-ref script)))
-               nil "No files found to expand %s"
-               (or (dwim-shell-command--contains-multi-file-ref script)
-                   (dwim-shell-command--contains-single-file-ref script))))
-  (when extensions
-    (seq-do (lambda (file)
-              (cl-assert (seq-contains-p extensions (downcase (file-name-extension file)))
-                         nil "Not a .%s file" (string-join extensions " .")))
-            files))
-  (seq-do (lambda (util)
-            (cl-assert (executable-find util) nil (format "%s not installed" util)))
-          utils)
-  (let* ((replacements (dwim-shell-command--extract-queries script))
-         (proc-buffer (generate-new-buffer (format "*%s*" buffer-name)))
-         (template script)
-         (script "")
-         (files-before)
-         (proc)
-         (progress-reporter)
-         (padding (dwim-shell-command--digits (length files)))
-         (n (or (dwim-shell-command--n-start-value template padding) "1")))
-    (if (seq-empty-p files)
-        (setq script (dwim-shell-command--expand-file-template template nil post-process-template gen-temp-dir n replacements))
-      (if (dwim-shell-command--contains-multi-file-ref template)
-          (setq script (dwim-shell-command--expand-files-template template files post-process-template gen-temp-dir replacements join-separator))
-        (seq-do (lambda (file)
-                  (setq script
-                        (concat script "\n"
-                                (dwim-shell-command--expand-file-template template file post-process-template gen-temp-dir n replacements)))
-                  (setq n (dwim-shell-command--increment-string n padding)))
-                files)))
-    (setq script (string-trim script))
-    (with-current-buffer proc-buffer
-      (let ((inhibit-message t))
-      ;; Silence noise of entering shell-mode.
-        (shell-mode))
-      (setq default-directory default-directory)
-      (shell-command-save-pos-or-erase)
-      (view-mode +1)
-      (setq view-exit-action 'kill-buffer))
-    (setq files-before (dwim-shell-command--default-directory-files monitor-directory))
-    (setq proc (apply #'start-process (seq-concatenate 'list
-                                                       (list (buffer-name proc-buffer) proc-buffer)
-                                                       shell-util
-                                                       shell-args
-                                                       (if shell-pipe
-                                                           (list (format "echo '%s' | %s" script shell-pipe))
-                                                         (list script)))))
-    (set-process-query-on-exit-flag proc nil)
-    (if no-progress
-        (dwim-shell-command--message "%s started" (process-name proc))
-      (setq progress-reporter (make-progress-reporter
-                               ;; Append space so "done" is spaced when
-                               ;; progress reporter is finished:
-                               ;;
-                               ;; *DWIM shell command* done
-                               (concat (process-name proc) " ")))
-      (progress-reporter-update progress-reporter))
-    ;; Momentarily set buffer to same window, so it's next in recent stack.
-    ;; Makes finding the shell command buffer a lot easier.
-    (let ((current (current-buffer)))
-      (pop-to-buffer-same-window proc-buffer)
-      (pop-to-buffer-same-window current))
-    (when focus-now
-      (switch-to-buffer proc-buffer))
-    (if (equal (process-status proc) 'exit)
-        (dwim-shell-command--finalize (current-buffer)
-                                      files-before
-                                      proc
-                                      progress-reporter
-                                      on-completion
-                                      silent-success
-                                      error-autofocus
-                                      monitor-directory)
-      (setq dwim-shell-command--commands
-            (push (cons (process-name proc)
-                        (make-dwim-shell-command--command :script script
-                                                          :process proc
-                                                          :name (process-name proc)
-                                                          :calling-buffer (current-buffer)
-                                                          :files-before files-before
-                                                          :reporter progress-reporter
-                                                          :on-completion on-completion
-                                                          :silent-success silent-success
-                                                          :error-autofocus error-autofocus
-                                                          :monitor-directory monitor-directory))
-                  dwim-shell-command--commands))
-      (set-process-sentinel proc #'dwim-shell-command--sentinel)
-      (set-process-filter proc #'dwim-shell-command--filter))))
-
-(cl-defun dwim-shell-command-read-file-name (prompt &key extension default)
-  "Invoke `read-string' with PROMPT.
-Validates :EXTENSION and returns :DEFAULT if empty input."
-  (let ((file-name (read-string prompt)))
-    (cond ((string-empty-p (string-trim file-name))
-           default)
-          ((and extension
-                (string-equal (file-name-extension file-name) extension))
-           file-name)
-          ((and extension
-                (not (string-equal (file-name-extension file-name) extension)))
-           (user-error "Name must end in .%s" extension))
-          (t
-           file-name))))
-
-(defun dwim-shell-command--message (message &rest args)
-  "Like `dwim-shell-command--message' but non-blocking.
-MESSAGE and ARGS same as `dwim-shell-command--message'."
-  (let ((message-id (random)))
-    (message (propertize (apply #'format message args) 'message-id message-id))
-    (run-with-timer 3 nil
-                    (lambda ()
-                      (when (and (current-message)
-                                 (eq (get-text-property 0 'message-id (current-message))
-                                     message-id))
-                        (message nil))))))
-
-(defun dwim-shell-command--extract-queries (template)
-  "Extract queries from TEMPLATE.
-
-For all queries, request a value from the user.
-
-For example:
-
-  \"Hello <<Width:100>> world <<Height:200>>\" =>
-
-    ((\"<<Width:100>>\" . \"100\")
-     (\"<<Height:200>>\" . \"200\"))"
-  (let ((matches)
-        (pos 0))
-    (while (and (< pos (length template))
-                (string-match "<<\\([[:alpha:]]\\|[[:blank:]]\\)+:\\([[:alnum:]]\\|[.]\\)*>>" template pos))
-      (setq pos (1+ (match-beginning 0)))
-      (let ((match 0))
-        (push (match-string match template) matches)
-        (setq match (1+ match))))
-    (seq-map (lambda (match)
-               (let* ((query (split-string (string-remove-suffix ">>" (string-remove-prefix "<<" match)) ":"))
-                      (prompt (nth 0 query))
-                      (default-value (if (string-empty-p (nth 1 query))
-                                         nil
-                                       (nth 1 query)))
-                      (value (if (string-match-p "^\\([[:digit:]]\\|[.]\\)+$" default-value)
-                                 (number-to-string (read-number (format "%s: " prompt)
-                                                                (string-to-number default-value)))
-                               (string-trim (read-string (concat prompt
-                                                                 (if default-value
-                                                                     (format " (default %s): " default-value)
-                                                                   ": "))))))
-                      (result (cons match (if (string-empty-p value)
-                                              default-value
-                                            value))))
-                 (cl-assert (cdr result) nil "Must have a value")result))
-             (seq-uniq (nreverse matches)))))
-
-(defun dwim-shell-command--digits (n)
-  "Return the number of digits in N."
-  (let ((count 0))
-    (while (> n 0)
-      (setq n (/ n 10))
-      (setq count (1+ count)))
-    count))
-
-(defun dwim-shell-command--number-to-string (n padding)
-  "Convert N to string using PADDING for number of digits."
-  (format (format "%%0%dd" padding) n))
-
-(defun dwim-shell-command--expand-files-template (template files &optional post-process-template temp-dir replacements join-separator)
-  "Expand TEMPLATE using FILES.
-
-Expand using <<*>> for FILES.
-
-Note: This expander cannot be used to expand <<f>>, <<fne>>, or <<e>>.
-
-  For example:
-
-    Given FILES '(\"path/to/image1.png\" \"path/to/image2.png\")
-
-    \"du -csh '<<*>>'\" expands to
-
-      \"du -csh 'path/to/image1.png' 'path/to/image2.png'\"
-
-Use POST-PROCESS-TEMPLATE to further expand template given own logic.
-
-Set TEMP-DIR to a unique temp directory to this template.
-
-REPLACEMENTS is a cons list of literals to replace with values.
-
-JOIN-SEPARATOR is used to join files from <<*>>."
-  (cl-assert (not (and (dwim-shell-command--contains-multi-file-ref template)
-                       (dwim-shell-command--contains-single-file-ref template)))
-             nil "Must not have %s and %s in the same template"
-             (dwim-shell-command--contains-multi-file-ref template)
-             (dwim-shell-command--contains-single-file-ref template))
-  (setq files (seq-map (lambda (file)
-                         (expand-file-name file))
-                       files))
-
-  (mapc (lambda (replacement)
-          (setq template
-                (string-replace (car replacement) (cdr replacement) template)))
-        replacements)
-
-  ;; Try to use quotes surrounding <<*>> in each path.
-  ;; "'<<*>>'" with '("path/to/image1.png" "path/to/image2.png") -> "'path/to/image1.png' 'path/to/image2.png'"
-  (when-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<\\*\>\>" template))
-              (unescaped-quote (nth 0 quoting))
-              (escaped-quote (nth 1 quoting)))
-    (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<\\*\>\>\\)\\([^ ]\\)"
-                                             (string-join (seq-map (lambda (file)
-                                                                     (concat unescaped-quote
-                                                                             (string-replace unescaped-quote
-                                                                                             escaped-quote file) unescaped-quote))
-                                                                   files)
-                                                          (or join-separator " "))
-                                             template nil nil 0)))
-
-  ;; "<<some.txt(u)>>" -> some.txt (if unique)
-  ;;                   -> some(1).txt (if it exist)
-  (when-let* ((found (string-match "\<\<\\([^ ]?+\\)(u)\>\>" template))
-              (name (match-string 1 template)))
-    (setq template (replace-regexp-in-string "\<\<\\([^ ]?+\\)(u)\>\>"
-                                             (dwim-shell-command--unique-new-file-path name)
-                                             template nil nil 0)))
-
-  ;; "<<~>>" -> "/home/user" (or equivalent).
-  (when (string-match "\<\<~\>\>" template)
-    (setq template (replace-regexp-in-string "\<\<~\>\>"
-                                             (expand-file-name "~")
-                                             template nil nil 0)))
-
-  ;; "<<*>>" with '("path/to/image1.png" "path/to/image2.png") -> "path/to/image1.png path/to/image2.png"
-  (setq template (replace-regexp-in-string "\\(\<\<\\*\>\>\\)" (string-join files (or join-separator " ")) template nil nil 1))
-
-  ;; "<<td>>" with TEMP-DIR -> "/var/folders/m7/ky091cp56d5g68nyhl4y7frc0000gn/T/dwim-shell-command-JNK4V5"
-  (setq template (replace-regexp-in-string "\\(\<\<td\>\>\\)" temp-dir template nil nil 1))
-
-  ;; "<<cb>>" with (current-kill 0) -> "whatever was in kill ring"
-  (when (string-match "\<\<cb\>\>" template)
-    (setq template (replace-regexp-in-string "\\(\<\<cb\>\>\\)" (current-kill 0)
-                                             template nil nil 1)))
-
-  (when post-process-template
-    (setq template (funcall post-process-template template files)))
-  template)
-
-(defun dwim-shell-command--escaped-quote-around (needle haystack &optional unbalanced)
-  "Find NEEDLE in HAYSTACK that's surrounded by either ' or \".
-
-Set UNBALANCED to t if NEEDLE isn't surrounded by quotes on both sides.
-
-For example:
-
- \"\<\<fne\>\>\" \"before \"<<fne>>\" after\" => (\"\"\" \"\\\\\"\")
-
- \"\<\<fne\>\>\" \"before '<<fne>>' after\" => (\"'\" \"'\"'\"'\")"
-  (when-let ((found (string-match (format "\\([^ ]\\)\\(%s\\)\\([^ ]\\)" needle) haystack))
-             (unescaped-quote (if unbalanced
-                                  (or (match-string 1 haystack)
-                                      (match-string 3 haystack))
-                                (cl-assert (string-equal (match-string 1 haystack)
-                                                         (match-string 3 haystack)) nil
-                                                         "%s must match %s"
-                                                         (match-string 1 haystack)
-                                                         (match-string 3 haystack))
-                                (match-string 1 haystack)))
-             (escaped-quote "'"))
-    ;; Known quoted quotes.
-    (cond
-     ((string-equal unescaped-quote "\"")
-      (setq escaped-quote "\\\\\""))
-     ((string-equal unescaped-quote "'")
-      (setq escaped-quote "'\"'\"'"))
-     (t
-      (error "Couldn't figure out how to quote for \"%s\" using %s and %s"
-             haystack
-             (match-string 1 haystack)
-             (match-string 3 haystack))))
-    (list unescaped-quote escaped-quote)))
-
-(defun dwim-shell-command--expand-file-template (template file &optional post-process-template temp-dir current replacements)
-  "Expand TEMPLATE using FILE.
-
-Expand using <<f>> for FILE, <<fne>> for FILE without extension, and
-<<e>> for FILE extension.  <<n>>, <<1n>>, or <<an>> is replaced with
-CURRENT.  <<some.txt(u)>> expands to unique \"some(1).txt\".
-
-Note: This expander cannot be used to expand <<*>>.
-
-  For example:
-
-    Given FILE \"path/to/image.png\"
-
-    \"convert '<<f>>' '<<fne>>.jpg'\" expands to
-
-      \"convert 'path/to/image.png' 'path/to/image.jpg'\"
-
-Use POST-PROCESS-TEMPLATE to further expand template given own logic.
-
-Set TEMP-DIR to a unique temp directory to this template.
-
-REPLACEMENTS is a cons list of literals to replace with values."
-  (cl-assert (not (and (dwim-shell-command--contains-multi-file-ref template)
-                       (dwim-shell-command--contains-single-file-ref template)))
-             nil "Must not have %s and %s in the same template"
-             (dwim-shell-command--contains-multi-file-ref template)
-             (dwim-shell-command--contains-single-file-ref template))
-
-  (mapc (lambda (replacement)
-          (setq template
-                (string-replace (car replacement) (cdr replacement) template)))
-        replacements)
-
-  (when file
-    (setq file (expand-file-name file))
-    ;; "<<fne>>" with "/path/tmp.txt" -> "/path/tmp"
-    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<fne\>\>" template t))
-              (unescaped-quote (nth 0 quoting))
-              (escaped-quote (nth 1 quoting)))
-        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<fne\>\>\\)"
-                                                 (string-replace unescaped-quote escaped-quote (file-name-sans-extension file))
-                                                 template nil nil 2))
-      (setq template (replace-regexp-in-string "\\(\<\<fne\>\>\\)" (file-name-sans-extension file) template nil nil 1)))
-
-    ;; "<<fbn>>" with "/path/tmp.txt" -> "tmp.txt"
-    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<fbn\>\>" template t))
-              (unescaped-quote (nth 0 quoting))
-              (escaped-quote (nth 1 quoting)))
-        (setq template (replace-regexp-in-string "\\(\<\<fbn\>\>\\)\\([^ ]\\)"
-                                                 (string-replace unescaped-quote escaped-quote (file-name-nondirectory file))
-                                                 template nil nil 1))
-      (setq template (replace-regexp-in-string "\\(\<\<fbn\>\>\\)" (file-name-nondirectory file) template nil nil 1)))
-
-    ;; "<<e>>" with "/path/tmp.txt" -> "txt"
-    (if (file-name-extension file)
-        (setq template (replace-regexp-in-string "\\(\<\<e\>\>\\)" (file-name-extension file) template nil nil 1))
-      ;; File had no extension. Attempt to remove .<<e>>.
-      (setq template (replace-regexp-in-string "\\(\.\<\<e\>\>\\)" "" template nil nil 1)))
-
-    ;; "<<f>>" with "/path/file.jpg" -> "/path/file.jpg"
-    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<f\>\>" template))
-              (unescaped-quote (nth 0 quoting))
-              (escaped-quote (nth 1 quoting)))
-        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<f\>\>\\)\\([^ ]\\)"
-                                                 (string-replace unescaped-quote escaped-quote file)
-                                                 template nil nil 2))
-      (setq template (replace-regexp-in-string "\\(\<\<f\>\>\\)" file template nil nil 1)))
-
-    ;; "<<f(u)>>" with "/path/file.jpg" -> "/path/file(1).jpg"
-    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<f(u)\>\>" template))
-              (unescaped-quote (nth 0 quoting))
-              (escaped-quote (nth 1 quoting)))
-        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<f\(u)>\>\\)\\([^ ]\\)"
-                                                 (string-replace unescaped-quote escaped-quote
-                                                                 (dwim-shell-command--unique-new-file-path file))
-                                                 template nil nil 2))
-      (setq template (replace-regexp-in-string "\\(\<\<f(u)\>\>\\)"
-                                               (dwim-shell-command--unique-new-file-path file) template nil nil 1))))
-
-  ;; "<<some.txt(u)>>" -> some.txt (if unique)
-  ;;                   -> some(1).txt (if it exist)
-  (when-let* ((found (string-match "\<\<\\([^ ]?+\\)(u)\>\>" template))
-              (name (match-string 1 template)))
-    (setq template (replace-regexp-in-string "\<\<\\([^ ]?+\\)(u)\>\>"
-                                             (dwim-shell-command--unique-new-file-path name)
-                                             template nil nil 0)))
-
-  ;; "<<~>>" -> "/home/user" (or equivalent).
-  (when (string-match "\<\<~\>\>" template)
-    (setq template (replace-regexp-in-string "\<\<~\>\>"
-                                             (expand-file-name "~")
-                                             template nil nil 0)))
-
-  ;; "<<td>>" with TEMP-DIR -> "/var/folders/m7/ky091cp56d5g68nyhl4y7frc0000gn/T/dwim-shell-command-JNK4V5"
-  (setq template (replace-regexp-in-string "\\(\<\<td\>\>\\)" temp-dir template nil nil 1))
-
-  ;; "<<cb>>" with (current-kill 0) -> "whatever was in kill ring"
-  (when (string-match "\<\<cb\>\>" template)
-    (setq template (replace-regexp-in-string "\\(\<\<cb\>\>\\)" (current-kill 0)
-                                             template nil nil 1)))
-
-  ;; "<<n>>" or "<<an>" or "<<1n>" with current.
-  (setq template (replace-regexp-in-string "\\(\<\<[[:alnum:]]?+n\>\>\\)" current
-                                           template nil nil 1))
-
-  (when post-process-template
-    (setq template (funcall post-process-template template file)))
-  template)
-
-(defun dwim-shell-command--contains-single-file-ref (template)
-  "Check for <<f>>, <<fne>>, or <<e>> in TEMPLATE."
-  (cond ((string-match "\<\<f\>\>" template)
-         "<<f>>")
-        ((string-match "\<\<fne\>\>" template)
-         "<<fne>>")
-        ((string-match "\<\<fbn\>\>" template)
-         "<<fbn>>")
-        ((string-match "\<\<e\>\>" template)
-         "<<e>>")))
-
-(defun dwim-shell-command--contains-multi-file-ref (template)
-  "Check for <<*>> in TEMPLATE."
-  (when (string-match "\<\<\\*\>\>" template)
-    "<<*>>"))
-
-(defun dwim-shell-command--default-directory-files (override)
-  "List of files in current buffer's `default-directory'.
-Use OVERRIDE to override `default-directory'."
-  (when-let ((default-directory (or override default-directory)))
-    (seq-map (lambda (filename)
-               (file-name-concat default-directory filename))
-             (cl-remove-if
-              (lambda (e) (member e '("." "..")))
-              (directory-files default-directory)))))
-
-(defun dwim-shell-command--last-modified-between (before after)
-  "Compare files in BEFORE and AFTER and return oldest file in diff."
-  (car (last (seq-sort #'file-newer-than-file-p
-                       (seq-difference after before)))))
-
-(defun dwim-shell-command--finalize (calling-buffer files-before process progress-reporter on-completion silent-success error-autofocus monitor-directory)
-  "Finalize script execution.
-
-CALLING-BUFFER, FILES-BEFORE, PROCESS, PROGRESS-REPORTER,
-ERROR-AUTOFOCUS, ON-COMPLETION, SILENT-SUCCESS, and MONITOR-DIRECTORY are
-all needed to finalize processing."
-  (let ((oldest-new-file)
-        (files-after))
-    (when progress-reporter
-      (progress-reporter-done progress-reporter))
-    (if (= (process-exit-status process) 0)
-        (progn
-          (dwim-shell-command--message (funcall dwim-shell-command-done-buffer-name (process-name process)))
-          (with-current-buffer (process-buffer process)
-            (rename-buffer (generate-new-buffer-name (funcall dwim-shell-command-done-buffer-name (process-name process)))))
-          (if on-completion
-              (funcall on-completion (process-buffer process) process)
-            (with-current-buffer calling-buffer
-              (if (equal major-mode 'dired-mode)
-                  (progn (when revert-buffer-function
-                           (funcall revert-buffer-function nil t))
-                         ;; Region is not accurate if new files added. Wipe it.
-                         (when (use-region-p)
-                           (deactivate-mark)))
-                (if (or buffer-auto-save-file-name
-                        buffer-file-name)
-                    (revert-buffer :ignore-auto :noconfirm)))
-              (setq files-after (dwim-shell-command--default-directory-files monitor-directory))
-              (setq oldest-new-file
-                    (dwim-shell-command--last-modified-between
-                     files-before
-                     files-after))
-              ;; There's at least one new file. Show that.
-              (if oldest-new-file
-                  (dired-jump nil oldest-new-file)
-                ;; Files may have been deleted but harder to track.
-                ;; Open dired and refresh to show files are gone.
-                (unless (equal (length files-after)
-                               (length files-before))
-                  (dired monitor-directory)
-                  (when revert-buffer-function
-                    (funcall revert-buffer-function nil t)))))
-            (unless (equal (process-buffer process)
-                           (window-buffer (selected-window)))
-              (if (or oldest-new-file silent-success)
-                  (kill-buffer (process-buffer process))
-                (unless silent-success
-                  (switch-to-buffer (process-buffer process)))))))
-      (if on-completion
-          (funcall on-completion (process-buffer process) process)
-        (if (and (buffer-name (process-buffer process))
-                 (or error-autofocus
-                     ;; Buffer already selected. Don't ask.
-                     (equal (process-buffer process)
-                            (window-buffer (selected-window)))
-                     (ignore-error quit
-                       (y-or-n-p (format "%s error, see output? "
-                                         (buffer-name (process-buffer process)))))))
-            (progn
-              (with-current-buffer (process-buffer process)
-                (rename-buffer (generate-new-buffer-name (funcall dwim-shell-command-error-buffer-name (process-name process)))))
-              (when (or error-autofocus
-                        (equal (process-buffer process)
-                               (window-buffer (selected-window))))
-                (dwim-shell-command--message (funcall dwim-shell-command-error-buffer-name (process-name process))))
-              (switch-to-buffer (process-buffer process)))
-          (kill-buffer (process-buffer process)))))
-    (setq dwim-shell-command--commands
-          (map-delete dwim-shell-command--commands (process-name process)))))
-
-(cl-defun dwim-shell-command--unique-new-file-path (file-path &key expand)
-  "Return a unique FILE-PATH using :EXPAND to expand FILE-PATH.
-\"/tmp/blah.txt\" -> \"/tmp/blah(1).txt\"
-\"/tmp/blah\" -> \"/tmp/blah(1)\""
-  (let ((counter 1)
-        (name (file-name-sans-extension file-path))
-        (extension (file-name-extension file-path)))
-    (while (file-exists-p file-path)
-      (if extension
-          (setq file-path (format "%s(%d).%s" name counter extension))
-        (setq file-path (format "%s(%d)" name counter)))
-      (setq counter (1+ counter)))
-    (if expand
-        (expand-file-name file-path)
-      file-path)))
-
-(defun dwim-shell-command--sentinel (process _)
-  "Handles PROCESS sentinel and STATE."
-  (let ((exec (map-elt dwim-shell-command--commands (process-name process))))
-    (dwim-shell-command--finalize (dwim-shell-command--command-calling-buffer exec)
-                                  (dwim-shell-command--command-files-before exec)
-                                  process
-                                  (dwim-shell-command--command-reporter exec)
-                                  (dwim-shell-command--command-on-completion exec)
-                                  (dwim-shell-command--command-silent-success exec)
-                                  (dwim-shell-command--command-error-autofocus exec)
-                                  (dwim-shell-command--command-monitor-directory exec))))
-
-(defun dwim-shell-command--filter (process output)
-  "Handles PROCESS filtering and STATE and OUTPUT."
-  (when-let* ((exec (map-elt dwim-shell-command--commands (process-name process)))
-              (reporter (dwim-shell-command--command-reporter exec)))
-    (progress-reporter-update reporter))
-  (comint-output-filter process output))
-
-(defun dwim-shell-command--file-extensions ()
-  "Return buffer file extension or marked/region extensions for a `dired' buffer."
-  (seq-uniq
-   (seq-map
-    #'file-name-extension
-    (seq-filter
-     #'file-name-extension
-     (dwim-shell-command--files)))))
-
-(defun dwim-shell-command--files ()
-  "Return buffer file (if available) or marked/region files for a `dired' buffer."
-  (cl-assert (not (and (use-region-p) (let ((files (dired-get-marked-files nil nil nil t)))
-                                     ;; Based on `dired-number-of-marked-files'.
-                                     (cond ((null (cdr files))
-                                            nil)
-                                           ((and (= (length files) 2)
-                                                 (eq (car files) t))
-                                            t)
-                                           (t
-                                            (not (seq-empty-p files)))))))
-             nil "Region and marked files both active. Choose one only.")
-  (if (buffer-file-name)
-      (list (buffer-file-name))
-    (or
-     (dwim-shell-command--dired-paths-in-region)
-     (dired-get-marked-files))))
-
-(defun dwim-shell-command--dired-paths-in-region ()
-  "If `dired' buffer, return region files.  nil otherwise."
-  (when (and (equal major-mode 'dired-mode)
-             (use-region-p))
-    (let ((start (region-beginning))
-          (end (region-end))
-          (paths))
-      (save-excursion
-        (save-restriction
-          (goto-char start)
-          (while (< (point) end)
-            ;; Skip non-file lines.
-            (while (and (< (point) end) (dired-between-files))
-              (forward-line 1))
-            (when (dired-get-filename nil t)
-              (setq paths (append paths (list (dired-get-filename nil t)))))
-            (forward-line 1))))
-      paths)))
-
-(defun dwim-shell-command--n-start-value (template padding)
-  "Extract n start value from TEMPLATE using PADDING.
-Falls back to \"1\"."
-  (when (string-match "\<\<\\([[:alnum:]]?+\\)n\>\>" template)
-    (if (string-empty-p (match-string 1 template))
-        (dwim-shell-command--increment-string (number-to-string 0) padding)
-      (if-let* ((start-string (match-string 1 template))
-                (start-n (string-to-number start-string)))
-          (dwim-shell-command--increment-string (number-to-string (1- start-n)) padding)
-        (match-string 1 template)))))
-
-(defun dwim-shell-command--increment-string (text padding)
-  "Increment TEXT using PADDING.
-\"a\" -> \"b\"
-\"1\" -> \"2\""
-  (cond ((string-match "^[[:alpha:]]$" text) ;; char
-         (char-to-string (1+ (string-to-char (match-string 0 text)))))
-        ((string-match "^[[:digit:]]+$" text) ;; char
-         (dwim-shell-command--number-to-string (1+ (string-to-number (match-string 0 text))) padding))))
-
-(defun dwim-shell-command--program-test (program &rest args)
-  "Test that running PROGRAM with ARGS is successful."
-  (eq 0 (apply #'call-process program nil nil nil args)))
-
-(cl-defun dwim-shell-command-foreach (fun &key monitor-directory)
-  "Execute FUN for each file.
-Monitor :MONITOR-DIRECTORY for new file and `dired-jump' to it."
-  (let ((files (dwim-shell-command--files))
-        (files-before (dwim-shell-command--default-directory-files  monitor-directory))
-        (oldest-new-file)
-        (created-file)
-        (jump-to))
-    (mapc (lambda (file-path)
-            (setq created-file (or (funcall fun file-path)
-                                   created-file)))
-          files)
-    (setq oldest-new-file (dwim-shell-command--last-modified-between
-                           files-before
-                           (dwim-shell-command--default-directory-files monitor-directory)))
-    (setq jump-to (or oldest-new-file created-file))
-    (when jump-to
-      (dired-jump nil jump-to))))
-
-(provide 'dwim-shell-command)
-
-;;; dwim-shell-command.el ends here
diff --git a/elpa/dwim-shell-command-20230709.2129/dwim-shell-commands.el b/elpa/dwim-shell-command-20230709.2129/dwim-shell-commands.el
deleted file mode 100644 (file)
index 4fbaeca..0000000
+++ /dev/null
@@ -1,1152 +0,0 @@
-;;; dwim-shell-commands.el --- Useful commands -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2022 Alvaro Ramirez
-
-;; Author: Alvaro Ramirez
-;; URL: https://github.com/xenodium/dwim-shell-command
-
-;; This package 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, or (at your option)
-;; any later version.
-
-;; This package 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.  If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; A collection of useful commands created via
-;; `dwim-shell-command-on-marked-files'.
-
-;;; Code:
-
-(require 'browse-url)
-(require 'cl-lib)
-(require 'dwim-shell-command)
-(require 'files)
-(require 'proced)
-(require 'seq)
-(require 'subr-x)
-
-(defun dwim-shell-commands-audio-to-mp3 ()
-  "Convert all marked audio to mp3(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to mp3"
-   "ffmpeg -stats -n -i '<<f>>' -acodec libmp3lame '<<fne>>.mp3'"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-open-clipboard-url ()
-  "Open clipboard URL.  Offer to stream if possible."
-  (interactive)
-  (let ((url (or (current-kill 0)
-                 (user-error "Nothing in clipboard"))))
-    (dwim-shell-commands-url-browse url)))
-
-(defun dwim-shell-commands-url-browse (url &rest args)
-  "If URL is playable media, offer to open in mpv.  Else browser.
-Optional argument ARGS as per `browse-url-default-browser'"
-  (if (and (or (string-match-p "^http[s]?://.*youtube.com" url)
-               (string-match-p "^http[s]?://.*m.youtube.com" url)
-               (string-match-p "^http[s]?://.*youtu.be" url)
-               (string-match-p "^http[s]?://.*soundcloud.com" url)
-               (string-match-p "^http[s]?://.*redditmedia.com" url)
-               (string-match-p "^http[s]?://.*reddit.com" url)
-               (string-match-p "^http[s]?://.*bandcamp.com" url))
-           (y-or-n-p "Stream from mpv? "))
-      (dwim-shell-command-on-marked-files
-       "Streaming"
-       (format "mpv --geometry=30%%x30%%+100%%+0%% '%s'" url)
-       :utils "mpv"
-       :no-progress t
-       :error-autofocus t
-       :silent-success t)
-    (funcall #'browse-url-default-browser url args)))
-
-(defun dwim-shell-commands-stream-clipboard-url ()
-  "Stream clipboard URL using mpv."
-  (interactive)
-  (cl-assert (string-match-p "^http[s]?://" (current-kill 0)) nil "Not a URL")
-  (dwim-shell-command-on-marked-files
-   "Streaming"
-   "mpv --geometry=30%x30%+100%+0% \"<<cb>>\""
-   :utils "mpv"
-   :no-progress t
-   :error-autofocus t
-   :silent-success t))
-
-(defun dwim-shell-commands-download-clipboard-stream-url ()
-  "Download clipboard URL."
-  (interactive)
-  (cl-assert (string-match-p "^http[s]?://" (current-kill 0)) nil "Not a URL")
-  (dwim-shell-command-on-marked-files
-   "Downloading"
-   "youtube-dl --newline -o \"~/Downloads/%(title)s.%(ext)s\" \"<<cb>>\""
-   :utils "youtube-dl"
-   :no-progress t
-   :error-autofocus t
-   :monitor-directory "~/Downloads"
-   :silent-success t))
-
-(defun dwim-shell-commands-image-clear-exif-metadata ()
-  "Clear EXIF metadata in image(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "View EXIF"
-   "cp '<<f>>' '<<fne>>_cleared.<<e>>'
-    exiftool -all:all= -overwrite_original '<<fne>>_cleared.<<e>>'"
-   :utils "exiftool"))
-
-(defun dwim-shell-commands-image-exif-metadata ()
-  "View EXIF metadata in image(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "View EXIF"
-   "exiftool '<<f>>'"
-   :utils "exiftool"))
-
-(defun dwim-shell-commands-ocr-text-from-image ()
-  "Extract text from image via tesseract."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Extract text from image via tesseract."
-   "tesseract '<<f>>' -"
-   :utils "tesseract"))
-
-(defun dwim-shell-commands-image-browse-location ()
-  "Open image(s) location in browser."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Browse location"
-   "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\"
-    if [ -z \"$lat\" ]; then
-      echo \"no latitude\"
-      exit 1
-    fi
-    lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\"
-    if [ -z \"$lon\" ]; then
-      echo \"no longitude\"
-      exit 1
-    fi
-    if [[ $OSTYPE == darwin* ]]; then
-      open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\"
-    else
-      xdg-open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\"
-    fi"
-   :utils "exiftool"
-   :error-autofocus t
-   :silent-success t))
-
-(defun dwim-shell-commands-image-reverse-geocode-location ()
-  "Reverse geocode image(s) location."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Reverse geocode"
-   "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\"
-    if [ -z \"$lat\" ]; then
-      echo \"no latitude\"
-      exit 1
-    fi
-    lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\"
-    if [ -z \"$lon\" ]; then
-      echo \"no longitude\"
-      exit 1
-    fi
-    json=$(curl \"https://nominatim.openstreetmap.org/reverse?format=json&accept-language=en&lat=${lat}&lon=${lon}&zoom=18&addressdetails=1\")
-    echo \"json_start $json json_end\""
-   :utils '("exiftool" "curl")
-   :silent-success t
-   :error-autofocus t
-   :on-completion (lambda (buffer _process)
-                    (with-current-buffer buffer
-                      (goto-char (point-min))
-                      (let ((matches '()))
-                        (while (re-search-forward "^json_start\\(.*?\\)json_end" nil t)
-                          (push (match-string 1) matches))
-                        (message "%s" (string-join (seq-map (lambda (json)
-                                                              (map-elt (json-parse-string json :object-type 'alist) 'display_name))
-                                                            matches)
-                                                   "\n")))
-                      (kill-buffer buffer)))))
-
-(defun dwim-shell-commands-image-horizontal-flip ()
-  "Horizontally flip image(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Image horizontal flip"
-   "convert -verbose -flop '<<f>>' '<<fne>>_h_flipped.<<e>>'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-image-vertical-flip ()
-  "Horizontally flip image(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Image vertical flip"
-   "convert -verbose -flip '<<f>>' '<<fne>>_v_flipped.<<e>>'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-image-to-jpg ()
-  "Convert all marked images to jpg(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to jpg"
-   "convert -verbose '<<f>>' '<<fne>>.jpg'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-image-to-png ()
-  "Convert all marked images to png(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to png"
-   "convert -verbose '<<f>>' '<<fne>>.png'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-svg-to-png ()
-  "Convert all marked svg(s) to png(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to png"
-   "rsvg-convert -b white '<<f>>' -f png -o '<<fne>>.png'"
-   :utils "rsvg-convert"))
-
-(defun dwim-shell-commands-make-transparent-png ()
-  "Create a transparent png."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Create transparent png"
-   "convert -verbose -size <<width:200>>x<<height:200>> xc:none '<<empty<<width:200>>x<<height:200>>.png(u)>>'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-join-as-pdf ()
-  "Join all marked images as a single pdf."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Join as pdf"
-   (format "convert -verbose '<<*>>' '<<%s(u)>>'"
-           (dwim-shell-command-read-file-name
-            "Join as pdf named (default \"joined.pdf\"): "
-            :extension "pdf"
-            :default "joined.pdf"))
-   :utils "convert"))
-
-(defun dwim-shell-commands-join-images-horizontally ()
-  "Join all marked images horizontally as a single image."
-  (interactive)
-  (let ((filename (format "joined.%s"
-                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
-    (dwim-shell-command-on-marked-files
-     "Join images horizontally"
-     (format "convert -verbose '<<*>>' +append '<<%s(u)>>'"
-             (dwim-shell-command-read-file-name
-              (format "Join as image named (default \"%s\"): " filename)
-              :default filename))
-     :utils "convert")))
-
-(defun dwim-shell-commands-join-images-vertically ()
-  "Join all marked images vertically as a single image."
-  (interactive)
-  (let ((filename (format "joined.%s"
-                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
-    (dwim-shell-command-on-marked-files
-     "Join images vertically"
-     (format "convert -verbose '<<*>>' -append '<<%s(u)>>'"
-             (dwim-shell-command-read-file-name
-              (format "Join as image named (default \"%s\"): " filename)
-              :default filename))
-     :utils "convert")))
-
-(defun dwim-shell-commands-image-to-grayscale ()
-  "Convert all marked images to grayscale."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert image to grayscale"
-   "convert -verbose -type Grayscale '<<f>>' '<<fne>>_grayscale.<<e>>'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-reorient-image ()
-  "Reorient images."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Reorient image"
-   "convert -verbose -auto-orient '<<f>>' '<<fne>>_reoriented.<<e>>'"
-   :utils "convert"))
-
-(defun dwim-shell-commands-video-to-gif ()
-  "Convert all marked videos to gif(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to gif"
-   "ffmpeg -loglevel quiet -stats -y -i '<<f>>' -pix_fmt rgb24 -r 15 '<<fne>>.gif'"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-video-to-webp ()
-  "Convert all marked videos to webp(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to webp"
-   "ffmpeg -i '<<f>>' -vcodec libwebp -filter:v fps=fps=10 -compression_level 3 -lossless 1 -loop 0 -preset default -an -vsync 0 '<<fne>>'.webp"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-video-to-hevc-mkv ()
-  "Convert all marked videos to hevc mkv."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert video to h265 "
-   "REPO_DIR=/tmp/other_video_transcoding
-    if ! [ -d \"$REPO_DIR\" ]
-    then
-      git clone https://github.com/donmelton/other_video_transcoding.git $REPO_DIR
-    fi
-    pushd $REPO_DIR
-    git pull origin master || echo \"skipping repo update...\"
-    popd
-    ruby $REPO_DIR/bin/other-transcode --hevc '<<f>>'"
-   :utils '("git" "ffmpeg" "mkvtoolnix" "mpv")))
-
-(defun dwim-shell-commands-video-to-optimized-gif ()
-  "Convert all marked videos to optimized gif(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to optimized gif"
-   "ffmpeg -loglevel quiet -stats -y -i '<<f>>' -pix_fmt rgb24 -r 15 '<<fne>>.gif'
-    gifsicle -O3 '<<fne>>.gif' --lossy=80 -o '<<fne>>.gif'"
-   :utils '("ffmpeg" "gifsicle")))
-
-(defun dwim-shell-commands-unzip ()
-  "Unzip all marked archives (of any kind) using `atool'."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Unzip" "atool --extract --explain '<<f>>'"
-   :utils "atool"))
-
-(defun dwim-shell-commands-optimize-gif ()
-  "Convert all marked videos to optimized gif(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to optimized gif"
-   "gifsicle -O3 '<<f>>' --lossy=90 -o '<<fne>>_optimized.gif'"
-   :utils '("ffmpeg" "gifsicle")))
-
-(defun dwim-shell-commands-speed-up-gif ()
-  "Speeds up gif(s)."
-  (interactive)
-  (let ((factor (string-to-number
-                 (completing-read "Speed up x times: " '("1" "1.5" "2" "2.5" "3" "4")))))
-    (dwim-shell-command-on-marked-files
-     "Speed up gif"
-     (format "gifsicle -U '<<f>>' <<frames>> -O2 -o '<<fne>>_x%s.<<e>>'" factor)
-     :extensions "gif" :utils '("gifsicle" "identify")
-     :post-process-template (lambda (script file)
-                              (string-replace "<<frames>>" (dwim-shell-commands--gifsicle-frames-every factor file) script)))))
-
-(defun dwim-shell-commands-clip-round-rect-gif ()
-  "Clip gif(s) with round rectangle."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Clip round rect gif(s)"
-   "width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 '<<f>>')
-    height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 '<<f>>')
-    convert -quiet -size \"${width}x${height}\" xc:none -fill black -draw \"roundRectangle 0,0,${width},${height} <<Width width:27>>,<<Width width:27>>\" '<<td>>/mask.png'
-    convert  '<<f>>' -coalesce -background black -alpha remove -alpha off '<<td>>/no_alpha.<<e>>'
-    # https://stackoverflow.com/a/66990135
-    convert '<<td>>/no_alpha.<<e>>' -quiet -coalesce -alpha extract null: \\( '<<td>>/mask.png' -alpha extract \\) -compose multiply -layers composite '<<td>>/alpha.gif'
-    convert '<<td>>/no_alpha.<<e>>' null: '<<td>>/alpha.gif' -quiet -alpha off -compose copy_opacity -layers composite '<<fne>>_rounded.<<e>>'
-    # Turn looping on.
-    mogrify -loop 0 '<<fne>>_rounded.<<e>>'
-    gifsicle -O3  '<<fne>>_rounded.<<e>>' --lossy=80 -o '<<fne>>_rounded.<<e>>'"
-   :extensions "gif"
-   :utils '("ffprobe" "convert")))
-
-(defun dwim-shell-commands-resize-gif ()
-  "Resize marked gif(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Resize marked gif(s)"
-   "gifsicle --scale <<Scaling factor:0.5>> '<<f>>' -o '<<fne>>_x<<Scaling factor:0.5>>.gif'"
-   :extensions "gif"
-   :utils "gifsicle"))
-
-(defun dwim-shell-commands-epub-to-org ()
-  "Convert epub(s) to org."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "epub to org"
-   "pandoc --from=epub --to=org '<<f>>' > '<<fne>>.org'"
-   :extensions "epub"
-   :utils "pandoc"))
-
-(defun dwim-shell-commands-docx-to-pdf ()
-  "Convert docx(s) to pdf (via latex)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "docx to pdf (via latex)"
-   "pandoc -t latex '<<f>>' -o '<<fne>>.pdf'"
-   :extensions "docx" ;; brew install mactex
-   :utils "pdflatex"))
-
-(defun dwim-shell-commands-kill-process ()
-  "Select and kill process."
-  (interactive)
-  (let* ((pid-width 5)
-         (comm-width 25)
-         (user-width 10)
-         (processes (proced-process-attributes))
-         (candidates
-          (mapcar (lambda (attributes)
-                    (let* ((process (cdr attributes))
-                           (pid (format (format "%%%ds" pid-width) (map-elt process 'pid)))
-                           (user (format (format "%%-%ds" user-width)
-                                         (truncate-string-to-width
-                                          (map-elt process 'user) user-width nil nil t)))
-                           (comm (format (format "%%-%ds" comm-width)
-                                         (truncate-string-to-width
-                                          (map-elt process 'comm) comm-width nil nil t)))
-                           (args-width (- (window-width) (+ pid-width user-width comm-width 3)))
-                           (args (map-elt process 'args)))
-                      (cons (if args
-                                (format "%s %s %s %s" pid user comm (truncate-string-to-width args args-width nil nil t))
-                              (format "%s %s %s" pid user comm))
-                            process)))
-                  processes))
-         (selection (map-elt candidates
-                             (completing-read "kill process: "
-                                              (seq-sort
-                                               (lambda (p1 p2)
-                                                 (string-lessp (nth 2 (split-string (string-trim (car p1))))
-                                                               (nth 2 (split-string (string-trim (car p2))))))
-                                               candidates) nil t)))
-         (prompt-title (format "%s %s %s"
-                               (map-elt selection 'pid)
-                               (map-elt selection 'user)
-                               (map-elt selection 'comm))))
-    (when (y-or-n-p (format "Kill %s?" prompt-title))
-      (dwim-shell-command-on-marked-files
-       (format "Kill %s" prompt-title)
-       (format "kill -9 %d" (map-elt selection 'pid))
-       :utils "kill"
-       :error-autofocus t
-       :silent-success t))))
-
-(defun dwim-shell-commands-macos-toggle-bluetooth-device-connection ()
-  "Toggle Bluetooth device connection."
-  (interactive)
-  (let* ((devices (seq-filter
-                   (lambda (line)
-                     ;; Keep lines like: af-8c-3b-b1-99-af - Device name
-                     (string-match-p "^[0-9a-f]\\{2\\}" line))
-                   (with-current-buffer (get-buffer-create "*BluetoothConnector*")
-                     (erase-buffer)
-                     ;; BluetoothConnector exits with 64 if no param is given.
-                     ;; Invoke with no params to get a list of devices.
-                     (unless (eq 64 (call-process "BluetoothConnector" nil (current-buffer)))
-                       (kill-buffer (current-buffer))
-                       (error (buffer-string)))
-                     (let ((lines (split-string (buffer-string) "\n")))
-                       (kill-buffer (current-buffer))
-                       lines))))
-         (candidates (mapcar (lambda (device)
-                               ;; key (device name) : value (address)
-                               (cons (nth 1 (split-string device " - "))
-                                     (nth 0 (split-string device " - "))))
-                             devices))
-         (selected-name (completing-read "Toggle connection: "
-                                         (seq-sort #'string-lessp candidates) nil t))
-         (address (map-elt candidates selected-name)))
-    (dwim-shell-command-on-marked-files
-     (format "Toggle %s" selected-name)
-     (format "BluetoothConnector %s --notify" address)
-     :utils "BluetoothConnector"
-     ;; :error-autofocus t
-     ;; :silent-success t
-     )))
-
-(defun dwim-shell-commands-macos-bin-plist-to-xml ()
-  "Convert binary plist to xml."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert binary plist to xml"
-   "plutil -convert xml1 -o '<<fne>>.xml' '<<f>>'"
-   :utils "plutil"))
-
-(defun dwim-shell-commands-macos-toggle-dark-mode ()
-  "Toggle macOS dark mode."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Toggle dark mode"
-   "dark-mode"
-   :utils "dark-mode" ;; brew install dark-mode
-   :silent-success t))
-
-(defun dwim-shell-commands-pdf-to-txt ()
-  "Convert pdf to txt."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "pdf to txt"
-   "pdftotext -layout '<<f>>' '<<fne>>.txt'"
-   :utils "pdftotext"))
-
-(defun dwim-shell-commands-resize-image ()
-  "Resize marked image(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Resize image"
-   (let ((factor (read-number "Resize scaling factor: " 0.5)))
-     (format "convert -resize %%%d '<<f>>' '<<fne>>_x%.2f.<<e>>'"
-             (* 100 factor) factor))
-   :utils "convert"))
-
-(defun dwim-shell-commands-pdf-password-protect ()
-  "Speeds up gif(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Password protect pdf"
-   (format "qpdf --verbose --encrypt '%s' '%s' 256 -- '<<f>>' '<<fne>>_enc.<<e>>'"
-           (read-passwd "user-password: ")
-           (read-passwd "owner-password: "))
-   :utils "qpdf"
-   :extensions "pdf"))
-
-(defun dwim-shell-commands--gifsicle-frames-every (skipping-every file)
-  "Generate frames SKIPPING-EVERY count for video FILE."
-  (string-join
-   (seq-map (lambda (n) (format "'#%d'" n))
-            (number-sequence 0 (string-to-number
-                                ;; Get total frames count.
-                                (seq-first (process-lines "identify" "-format" "%n\n" file)))
-                             skipping-every)) " "))
-
-(defun dwim-shell-commands-video-to-mp3 ()
-  "Drop audio from all marked videos."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert to mp3"
-   "ffmpeg -i '<<f>>' -vn -ab 128k -ar 44100 -y '<<fne>>.mp3'"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-video-trim-beginning ()
-  "Drop audio from all marked videos."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Trim beginning"
-   "ffmpeg -i '<<f>>' -y -ss <<Seconds:5>> -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'"
-   :silent-success t
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-video-trim-end ()
-  "Drop audio from all marked videos."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Trim beginning"
-   "ffmpeg -sseof -<<Seconds:5>> -i '<<f>>' -y -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'"
-   :silent-success t
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-drop-video-audio ()
-  "Drop audio from all marked videos."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Drop audio"
-   "ffmpeg -i '<<f>>' -c copy -an '<<fne>>_no_audio.<<e>>'"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-ping-google ()
-  "Ping duckduckgo.com."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Ping google.com"
-   "ping -c 3 google.com"
-   :utils "ping"
-   :focus-now t))
-
-(defun dwim-shell-commands-speed-up-video ()
-  "Speed up video(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Speed up video"
-   (let ((factor (read-number "Resize scaling factor: " 2)))
-     (format "ffmpeg -i '<<f>>' -an -filter:v 'setpts=%s*PTS' '<<fne>>_x%s.<<e>>'"
-             (/ 1 (float factor)) factor))
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-resize-video ()
-  "Resize marked images."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Resize video"
-   "
-eval $(ffprobe -v quiet -show_format -of flat=s=_ -show_entries stream=width '<<f>>');
-width=${streams_stream_0_width};
-zmodload zsh/mathfunc
-width=$((rint($width * <<Scaling factor:0.5>>)));
-# Make it even or face 'not divisible by 2' errors.
-if [[ $((width%2)) -ne 0 ]] then
-  width=$(($width - 1))
-fi
-ffmpeg -n -i '<<f>>' -vf \"scale=$width:-2\" '<<fne>>_x<<Scaling factor:0.5>>.<<e>>'"
-   :utils "ffmpeg"))
-
-(defun dwim-shell-commands-clipboard-to-qr ()
-  "Generate a QR code from clipboard."
-  (interactive)
-  (let ((temp-file (concat (temporary-file-directory) "qr-code")))
-    (dwim-shell-command-on-marked-files
-     "Generate a QR code from clipboard"
-     (format "qrencode -s10 -o %s %s" temp-file (shell-quote-argument (current-kill 0)))
-     :utils "qrencode"
-     :on-completion (lambda (buffer _process)
-                      (kill-buffer buffer)
-                      (switch-to-buffer (find-file-noselect temp-file t))))))
-
-(defun dwim-shell-commands-open-externally ()
-  "Open file(s) externally."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Open externally"
-   (if (eq system-type 'darwin)
-       "open '<<f>>'"
-     "xdg-open '<<f>>'")
-   :silent-success t
-   :utils (if (eq system-type 'darwin)
-              "open"
-            "xdg-open")))
-
-(defun dwim-shell-commands-macos-caffeinate ()
-  "Invoke caffeinate to prevent mac from sleeping."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Caffeinate"
-   "caffeinate"
-   :utils "caffeinate"
-   :no-progress t
-   :focus-now t))
-
-(defun dwim-shell-commands-macos-make-finder-alias ()
-  "Make macOS Finder alias."
-  (interactive)
-  (let ((files (dwim-shell-command--files))
-        (target-dir (read-directory-name "Select target dir: " "/Applications" nil t)))
-    (dwim-shell-command-on-marked-files
-     "Make macOS alias"
-     (format "osascript -e 'tell application \"Finder\" to make alias file to POSIX file \"<<f>>\" at POSIX file \"%s\"'"
-             target-dir)
-     :utils "osascript"
-     :no-progress t
-     :silent-success t
-     :on-completion (lambda (buffer _process)
-                      (kill-buffer buffer)
-                      (dired-jump nil (file-name-concat target-dir (file-name-nondirectory (nth 0 files))))))))
-
-(defun dwim-shell-commands-macos-version-and-hardware-overview-info ()
-  "View macOS version and hardware overview info."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "macOS hardware overview"
-   "sw_vers; system_profiler SPHardwareDataType"
-   :utils '("sw_vers" "system_profiler")))
-
-(defun dwim-shell-commands-macos-reveal-in-finder ()
-  "Reveal selected files in macOS Finder."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Reveal in Finder"
-   "import AppKit
-    NSWorkspace.shared.activateFileViewerSelecting([\"<<*>>\"].map{URL(fileURLWithPath:$0)})"
-   :silent-success t
-   :shell-pipe "swift -"
-   :join-separator ", "
-   :utils "swift"))
-
-(defun dwim-shell-commands--macos-sharing-services ()
-  "Return a list of sharing services."
-  (let* ((source (format "import AppKit
-                         NSSharingService.sharingServices(forItems: [
-                           %s
-                         ]).forEach {
-                           print(\"\\($0.title)\")
-                         }"
-                         (string-join (mapcar (lambda (file)
-                                                (format "URL(fileURLWithPath: \"%s\")" file))
-                                              (dwim-shell-command--files))
-                                      ", ")))
-         (services (split-string (string-trim (shell-command-to-string (format "echo '%s' | swift -" source)))
-                                 "\n")))
-    (when (seq-empty-p services)
-      (error "No sharing services available"))
-    services))
-
-(defun dwim-shell-commands-macos-share ()
-  "Share selected files from macOS."
-  (interactive)
-  (let* ((services (dwim-shell-commands--macos-sharing-services))
-         (service-name (completing-read "Share via: " services))
-         (selection (seq-position services service-name #'string-equal)))
-    (dwim-shell-command-on-marked-files
-     "Share"
-     (format
-      "import AppKit
-
-       _ = NSApplication.shared
-
-       NSApp.setActivationPolicy(.regular)
-
-       class MyWindow: NSWindow, NSSharingServiceDelegate {
-         func sharingService(
-           _ sharingService: NSSharingService,
-           didShareItems items: [Any]
-         ) {
-           NSApplication.shared.terminate(nil)
-         }
-
-         func sharingService(
-           _ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error
-         ) {
-           let error = error as NSError
-           if error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError {
-             NSApplication.shared.terminate(nil)
-           }
-           exit(1)
-         }
-       }
-
-       let window = MyWindow(
-         contentRect: NSRect(x: 0, y: 0, width: 0, height: 0),
-         styleMask: [],
-         backing: .buffered,
-         defer: false)
-
-       let services = NSSharingService.sharingServices(forItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)})
-       let service = services[%s]
-       service.delegate = window
-       service.perform(withItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)})
-
-       NSApp.run()" selection)
-     :silent-success t
-     :shell-pipe "swift -"
-     :join-separator ", "
-     :no-progress t
-     :utils "swift")))
-
-(defun dwim-shell-commands-macos-toggle-display-rotation ()
-  "Rotate display."
-  (interactive)
-  ;; #  Display_ID    Resolution  ____Display_Bounds____  Rotation
-  ;; 2  0x2b347692    1440x2560      0     0  1440  2560    270    [main]
-  ;; From fb-rotate output, get the `current-rotation' from Column 7, row 1 zero-based.
-  (let ((current-rotation (nth 7 (split-string (nth 1 (process-lines "fb-rotate" "-i"))))))
-    (dwim-shell-command-on-marked-files
-     "macOS hardware overview"
-     (format "fb-rotate -d 1 -r %s" (if (equal current-rotation "270") "0" "270"))
-     :utils "fb-rotate")))
-
-(defun dwim-shell-commands--macos-apps ()
-  "Return alist of macOS apps (\"Emacs\" . \"/Applications/Emacs.app\")."
-  (mapcar (lambda (path)
-            (cons (file-name-base path) path))
-          (seq-sort
-           #'string-lessp
-           (seq-mapcat (lambda (paths)
-                         (directory-files-recursively
-                          paths "\\.app$" t (lambda (path)
-                                              (not (string-suffix-p ".app" path)))))
-                       '("/Applications" "~/Applications" "/System/Applications")))))
-
-(defun dwim-shell-commands-macos-set-default-app ()
-  "Set default app for file(s)."
-  (interactive)
-  (let* ((apps (dwim-shell-commands--macos-apps))
-         (selection (progn
-                      (cl-assert apps nil "No apps found")
-                      (completing-read "Set default app: " apps nil t))))
-    (dwim-shell-command-on-marked-files
-     "Set default app"
-     (format "duti -s \"%s\" '<<e>>' all"
-             (string-trim
-              (shell-command-to-string (format "defaults read '%s/Contents/Info.plist' CFBundleIdentifier"
-                                               (map-elt apps selection)))))
-     :silent-success t
-     :no-progress t
-     :utils "duti")))
-
-(defun dwim-shell-commands-macos-open-with ()
-  "Open file(s) with specific external app."
-  (interactive)
-  (let* ((apps (dwim-shell-commands--macos-apps))
-         (selection (progn
-                      (cl-assert apps nil "No apps found")
-                      (completing-read "Open with: " apps nil t))))
-    (dwim-shell-command-on-marked-files
-     "Open with"
-     (format "open -a '%s' '<<*>>'" (map-elt apps selection))
-     :silent-success t
-     :no-progress t
-     :utils "open")))
-
-(defun dwim-shell-commands-macos-open-with-firefox ()
-  "Open file(s) with specific external app."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Open with Firefox"
-   "open -a Firefox '<<*>>'"
-   :silent-success t
-   :no-progress t
-   :utils "open"))
-
-(defun dwim-shell-commands-macos-open-with-safari ()
-  "Open file(s) with specific external app."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Open with Firefox"
-   "open -a Safari '<<*>>'"
-   :silent-success t
-   :no-progress t
-   :utils "open"))
-
-(defun dwim-shell-commands-macos-start-recording-window ()
-  "Select and start recording a macOS window."
-  (interactive)
-  (let* ((window (dwim-shell-commands--macos-select-window))
-         (path (dwim-shell-commands--generate-path "~/Desktop" (car window) ".gif"))
-         (buffer-file-name path) ;; override so <<f>> picks it up
-         (inhibit-message t))
-    ;; Silence echo to avoid unrelated messages making into animation.
-    (cl-letf (((symbol-function 'dwim-shell-command--message)
-               (lambda (fmt &rest args) nil)))
-      (dwim-shell-command-on-marked-files
-       "Start recording a macOS window."
-       (format
-        "macosrec --record '%s' --gif --output '<<f>>'"
-        (cdr window))
-       :silent-success t
-       :monitor-directory "~/Desktop"
-       :no-progress t
-       :utils '("ffmpeg" "macosrec")
-       :on-completion
-       (lambda (buffer process)
-         (if (= (process-exit-status process) 0)
-             (progn
-               "Saved recording"
-               (dired-jump nil path)
-               (kill-buffer buffer))
-           (with-current-buffer buffer
-             (goto-char (point-min))
-             (if (search-forward "Aborted" nil t)
-                 (progn
-                   (message "Aborted recording")
-                   (kill-buffer buffer))
-               (switch-to-buffer buffer)))))))))
-
-(defun dwim-shell-commands--generate-path (dir name ext)
-  "Generate a timestamped path with DIR, NAME, and EXT."
-  (concat (file-name-as-directory (expand-file-name dir))
-          (format-time-string "%Y-%m-%d-%H:%M:%S-")
-          name ext))
-
-(defun dwim-shell-commands--macos-select-window ()
-  "Return a list of macOS windows."
-  (if-let* ((line (completing-read
-                   "Select: "
-                   (process-lines "macosrec" "--list") nil t))
-            (window-info (split-string line " "))
-            (window-number (string-to-number (nth 0 window-info)))
-            (window-app (nth 1 window-info))
-            (valid (> window-number 0)))
-      (cons window-app window-number)
-    (user-error "No window found")))
-
-(defun dwim-shell-commands-macos-end-recording-window ()
-  "Stop recording a macOS window."
-  (interactive)
-  (let ((inhibit-message t))
-    (cl-letf (((symbol-function 'dwim-shell-command--message)
-               (lambda (fmt &rest args) nil)))
-      (dwim-shell-command-on-marked-files
-       "End recording macOS window."
-       "macosrec --save"
-       :silent-success t
-       :no-progress t
-       :error-autofocus t
-       :utils "macosrec"))))
-
-(defun dwim-shell-commands-macos-abort-recording-window ()
-  "Stop recording a macOS window."
-  (interactive)
-  (let ((inhibit-message t))
-    (cl-letf (((symbol-function 'dwim-shell-command--message)
-               (lambda (fmt &rest args) nil)))
-      (dwim-shell-command-on-marked-files
-       "Abort recording macOS window."
-       "macosrec --abort"
-       :silent-success t
-       :no-progress t
-       :utils "macosrec"))))
-
-(defun dwim-shell-commands-macos-screenshot-window ()
-  "Select and screenshot macOS window."
-  (interactive)
-  ;; Silence echo to avoid unrelated messages making into screenshot.
-  (let ((window (dwim-shell-commands--macos-select-window))
-        (inhibit-message t))
-    (dwim-shell-command-on-marked-files
-     "Start recording a macOS window."
-     (format "macosrec --screenshot %s" (cdr window))
-     :silent-success t
-     :monitor-directory "~/Desktop"
-     :no-progress t
-     :utils "macosrec")))
-
-(defun dwim-shell-commands-files-combined-size ()
-  "Get files combined file size."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Get files combined file size"
-   "du -csh '<<*>>'"
-   :utils "du"
-   :on-completion (lambda (buffer _process)
-                    (with-current-buffer buffer
-                      (message "Total size: %s"
-                               (progn
-                                 (re-search-backward "\\(^[ 0-9.,]+[A-Za-z]+\\).*total$")
-                                 (match-string 1))))
-                    (kill-buffer buffer))))
-
-(defun dwim-shell-commands-image-to-icns ()
-  "Convert png to icns icon."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Convert png to icns icon."
-   "
-    # Based on http://stackoverflow.com/questions/12306223/how-to-manually-create-icns-files-using-iconutil
-    # Note: png must be 1024x1024
-    mkdir '<<fne>>.iconset'
-    sips -z 16 16 '<<f>>' --out '<<fne>>.iconset/icon_16x16.png'
-    sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_16x16NOSPAM2x.png'
-    sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_32x32.png'
-    sips -z 64 64 '<<f>>' --out '<<fne>>.iconset/icon_32x32NOSPAM2x.png'
-    sips -z 128 128 '<<f>>' --out '<<fne>>.iconset/icon_128x128.png'
-    sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_128x128NOSPAM2x.png'
-    sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_256x256NOSPAM2x.png'
-    sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_512x512.png'
-    sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_256x256NOSPAM2x.png'
-    sips -z 1024 1024 '<<f>>' --out '<<fne>>.iconset/icon_512x512NOSPAM2x.png'
-    iconutil -c icns '<<fne>>.iconset'"
-   :utils '("sips" "iconutil")
-   :extensions "png"))
-
-(defun dwim-shell-commands-git-clone-clipboard-url-to-downloads ()
-  "Clone git URL in clipboard to \"~/Downloads/\"."
-  (interactive)
-  (cl-assert (string-match-p "^\\(http\\|https\\|ssh\\)://" (current-kill 0)) nil "No URL in clipboard")
-  (let* ((url (current-kill 0))
-         (download-dir (expand-file-name "~/Downloads/"))
-         (project-dir (concat download-dir (file-name-base url)))
-         (default-directory download-dir))
-    (when (or (not (file-exists-p project-dir))
-              (when (y-or-n-p (format "%s exists.  delete?" (file-name-base url)))
-                (delete-directory project-dir t)
-                t))
-      (dwim-shell-command-on-marked-files
-       (format "Clone %s" (file-name-base url))
-       (format "git clone %s" url)
-       :utils "git"
-       :on-completion (lambda (buffer _process)
-                        (kill-buffer buffer)
-                        (dired project-dir))))))
-
-(defun dwim-shell-commands-http-serve-dir ()
-  "HTTP serve current directory."
-  (interactive)
-  (cond ((executable-find "python3")
-         (dwim-shell-command-on-marked-files
-          "HTTP serve current dir"
-          "python3 -m http.server"
-          :utils "python3"
-          :focus-now t
-          :no-progress t))
-        ((executable-find "python2")
-         (dwim-shell-command-on-marked-files
-          "HTTP serve current dir"
-          "python2 -m SimpleHTTPServer"
-          :utils "python2"
-          :focus-now t
-          :no-progress t))
-        ((executable-find "python")
-         (dwim-shell-command-on-marked-files
-          "HTTP serve current dir"
-          "python -m SimpleHTTPServer"
-          :utils "python"
-          :focus-now t
-          :no-progress t))
-        (t
-         (error "No python found"))))
-
-(defun dwim-shell-commands-git-clone-clipboard-url ()
-  "Clone git URL in clipboard to `default-directory'."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   (format "Clone %s" (file-name-base (current-kill 0)))
-   "git clone <<cb>>"
-   :utils "git"))
-
-(defun dwim-shell-commands-pass-git-pull ()
-  "Pass git pull."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "pass git pull"
-   "pass git pull"
-   :utils '("pass" "git")
-   :silent-success t))
-
-(defun dwim-shell-commands-git-list-untracked-files ()
-  "List untracked git files in `default-directory'."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "List untracked"
-   "git ls-files --others ."
-   :utils "git"
-   :focus-now t))
-
-(defun dwim-shell-commands-git-delete-untracked-files ()
-  "Delete untracked git files in `default-directory'."
-  (interactive)
-  (when (y-or-n-p (format "Clean '%s'? \n\n%s\n...\n\n"
-                          default-directory
-                          (string-join
-                           (seq-take (process-lines "git" "ls-files" "--others" ".") 3)
-                           "\n")))
-    (dwim-shell-command-on-marked-files
-     "Clean untracked"
-     "git clean -f ."
-     :utils "git"
-     :silent-success t)))
-
-(defun dwim-shell-commands-external-ip ()
-  "Copy external IP to kill ring."
-  (interactive)
-  (let ((ip (car (last (process-lines "curl" "ifconfig.me")))))
-    (kill-new ip)
-    (message "Copied %s" ip)))
-
-(defun dwim-shell-commands-macos-install-iphone-device-ipa ()
-  "Install iPhone device .ipa.
-Needs ideviceinstaller and libmobiledevice installed."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Install .ipa"
-   "ideviceinstaller -i '<<f>>'"
-   :utils "ideviceinstaller"))
-
-(defun dwim-shell-commands-copy-to-downloads ()
-  "Copy file to ~/Downloads."
-  (interactive)
-  (dwim-shell-command-foreach
-   (lambda (file)
-     (copy-file file "~/Downloads/" 1)
-     (file-name-concat "~/Downloads" (file-name-nondirectory file)))
-   :monitor-directory "~/Downloads"))
-
-(defun dwim-shell-commands-duplicate ()
-  "Duplicate file."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Duplicate file(s)."
-   "cp -R '<<f>>' '<<f(u)>>'"
-   :utils "cp"))
-
-(defun dwim-shell-commands-rename-all ()
-  "Rename all marked file(s)."
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Rename all"
-   "mv '<<f>>' '<<New name:Renamed>>(<<n>>).<<e>>'"
-   :utils "mv"))
-
-(defun dwim-shell-commands-move-to-downloads ()
-  "Move file to ~/Downloads."
-  (interactive)
-  (dwim-shell-command-foreach
-   (lambda (file)
-     (rename-file file "~/Downloads/" 1)
-     (when (buffer-file-name)
-       (rename-buffer (file-name-nondirectory file))
-       (set-visited-file-name
-        (file-name-concat "~/Downloads" (file-name-nondirectory file)))
-       (set-buffer-modified-p nil))
-     (file-name-concat "~/Downloads" (file-name-nondirectory file)))
-   :monitor-directory "~/Downloads"))
-
-(defun dwim-shell-commands-copy-to-desktop ()
-  "Copy file to ~/Desktop."
-  (interactive)
-  (dwim-shell-command-foreach
-   (lambda (file)
-     (copy-file file "~/Desktop/" 1)
-     (file-name-concat "~/Desktop" (file-name-nondirectory file)))
-   :monitor-directory "~/Desktop"))
-
-(defun dwim-shell-commands-move-to-desktop ()
-  "Move file to ~/Desktop."
-  (interactive)
-  (dwim-shell-command-foreach
-   (lambda (file)
-     (rename-file file "~/Desktop/" 1)
-     (when (buffer-file-name)
-       (rename-buffer (file-name-nondirectory file))
-       (set-visited-file-name
-        (file-name-concat "~/Desktop" (file-name-nondirectory file)))
-       (set-buffer-modified-p nil))
-     (file-name-concat "~/Desktop" (file-name-nondirectory file)))
-   :monitor-directory "~/Desktop"))
-
-(defun dwim-shell-commands-kill-gpg-agent ()
-  "Kill (thus restart) gpg agent.
-
-Useful for when you get this error:
-
-gpg: public key decryption failed: No pinentry
-gpg: decryption failed: No pinentry"
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "Kill gpg agent"
-   "gpgconf --kill gpg-agent"
-   :utils "gpgconf"
-   :silent-success t))
-
-;; Based on
-;; https://apps.bram85.nl/git/bram/gists/src/commit/31ac3363da925daafa2420b7f96c67612ca28241/gists/dwim-0x0-upload.el
-(defun dwim-shell-commands-upload-to-0x0 ()
-  "Upload the marked files to 0x0.st"
-  (interactive)
-  (dwim-shell-command-on-marked-files
-   "0x0 upload"
-   "curl -Ffile=@<<f>> -Fsecret= https://0x0.st"
-   :utils "curl"
-   :post-process-template
-   ;; Insert the single quotes at the appropriate place according to
-   ;; 0x0.st example online:
-   ;; curl -F'file=@yourfile.png' -Fsecret= https://0x0.st
-   ;;
-   ;; The placement of these single quotes confuse the escaping
-   ;; mechanisms of dwim-shell-command, as it considers @ as the
-   ;; opening 'quote' as it appears right in front of <<f>>.
-   (lambda (template path)
-     (string-replace "-Ffile" "-F'file"
-                     (string-replace path (concat path "'") template)))
-   :on-completion
-   (lambda (buffer process)
-     (if (= (process-exit-status process) 0)
-         (with-current-buffer buffer
-           (let ((url (car (last (split-string (string-trim (buffer-string)) "\n")))))
-             (eww url)
-             (kill-new url)
-             (message "Copied: %s" (current-kill 0)))
-           (kill-buffer buffer))
-       (switch-to-buffer buffer)))))
-
-(provide 'dwim-shell-commands)
-
-;;; dwim-shell-commands.el ends here
diff --git a/elpa/dwim-shell-command-20241115.845/dwim-shell-command-autoloads.el b/elpa/dwim-shell-command-20241115.845/dwim-shell-command-autoloads.el
new file mode 100644 (file)
index 0000000..534831c
--- /dev/null
@@ -0,0 +1,335 @@
+;;; dwim-shell-command-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from dwim-shell-command.el
+
+(autoload 'dwim-shell-command "dwim-shell-command" "\
+Execute DWIM shell command asynchronously using noweb templates.
+
+Which files
+
+  `dwim-shell-command' attempts to guess which file(s) you may want
+  the command to operate on.
+
+  1. If visiting a `dired' buffer, draw the marked file(s).
+  2. If visiting any other buffer with an associated file, use that.
+
+Templates
+
+  Operate on drawn files using either the following:
+
+    <<f>> (file path)
+    <<fne>> (file path without extension)
+    <<e>> (extension)
+    <<td>> (generate a temporary directory)
+    <<*>> (all files joined)
+    <<cb>> (clipboard)
+    <<n>>, <<1n>>, or <<An>> (for current iteration)
+
+  For example:
+
+    With drawn files '(\"path/to/image1.png\" \"path/to/image2.png\")
+
+   \"convert '<<f>>' '<<fne>>.jpg'\" expands to
+
+     \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
+     \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
+
+   while \"ls -lh <<*>>\" expands to
+
+     \"ls -lh path/to/image1.png path/to/image2.png\"
+
+Focus
+
+  `dwim-shell-command' creates a process buffer to capture command
+  output, but doesn't display or focus on it by default.  Instead,
+  it tries to guess what's more convenient to focus on.
+
+  While the process is busy, show a spinner in the minibuffer.  No
+  focus changes.
+
+  After process is finished:
+
+  1. If there were any files created in the `default-directory',
+  jump to a `dired' buffer and move point to the new file (via
+  `dired-jump').
+
+  2. If no new files were created, automatically switch focus to the
+  process buffer and display its output.
+
+    Note: You can prevent this automatic focus by prepending your
+    command with whitespace.
+
+      |
+      V
+    \" convert '<<f>>' '<<fne>>.jpg'\"
+
+  3. If the shell command caused any errors, offer to focus the
+  process buffer and display its output.
+
+Quick exit
+
+  Process buffers are read-only and can be quickly closed by
+  pressing `q'.
+
+Prefix
+
+  With PREFIX, execute command that number of times.
+
+(fn PREFIX)" t)
+(register-definition-prefixes "dwim-shell-command" '("dwim-shell-command-"))
+
+\f
+;;; Generated autoloads from dwim-shell-commands.el
+
+(autoload 'dwim-shell-commands-audio-to-mp3 "dwim-shell-commands" "\
+Convert all marked audio to mp3(s)." t)
+(autoload 'dwim-shell-extract-har-content "dwim-shell-commands" "\
+Extract all har content fields to files.
+
+(fn PREFIX)" t)
+(autoload 'dwim-shell-extract-har-urls "dwim-shell-commands" "\
+Get all request URLs." t)
+(autoload 'dwim-shell-view-open-ports-per-app "dwim-shell-commands" "\
+View open ports per app" t)
+(autoload 'dwim-shell-commands-open-clipboard-url "dwim-shell-commands" "\
+Open clipboard URL.  Offer to stream if possible." t)
+(autoload 'dwim-shell-commands-stream-clipboard-url "dwim-shell-commands" "\
+Stream clipboard URL using mpv." t)
+(autoload 'dwim-shell-commands-download-clipboard-stream-url "dwim-shell-commands" "\
+Download clipboard URL." t)
+(autoload 'dwim-shell-commands-image-clear-exif-metadata "dwim-shell-commands" "\
+Clear EXIF metadata in image(s)." t)
+(autoload 'dwim-shell-commands-image-scan-code "dwim-shell-commands" "\
+Scan any code (ie. qr, bar, etc) from image(s)." t)
+(autoload 'dwim-shell-commands-image-exif-metadata "dwim-shell-commands" "\
+View EXIF metadata in image(s)." t)
+(autoload 'dwim-shell-commands-tesseract-ocr-text-from-image "dwim-shell-commands" "\
+Extract text from image via tesseract." t)
+(autoload 'dwim-shell-commands-image-view-location-in-openstreetmap "dwim-shell-commands" "\
+Open image(s) location in map/browser." t)
+(autoload 'dwim-shell-commands-image-reverse-geocode-location "dwim-shell-commands" "\
+Reverse geocode image(s) location." t)
+(autoload 'dwim-shell-commands-image-horizontal-flip "dwim-shell-commands" "\
+Horizontally flip image(s)." t)
+(autoload 'dwim-shell-commands-image-vertical-flip "dwim-shell-commands" "\
+Horizontally flip image(s)." t)
+(autoload 'dwim-shell-commands-image-to-jpg "dwim-shell-commands" "\
+Convert all marked images to jpg(s)." t)
+(autoload 'dwim-shell-commands-image-to-png "dwim-shell-commands" "\
+Convert all marked images to png(s)." t)
+(autoload 'dwim-shell-commands-svg-to-png "dwim-shell-commands" "\
+Convert all marked svg(s) to png(s)." t)
+(autoload 'dwim-shell-commands-svg-to-favicons "dwim-shell-commands" "\
+Convert svg to common favicons." t)
+(autoload 'dwim-shell-commands-make-transparent-png "dwim-shell-commands" "\
+Create a transparent png." t)
+(autoload 'dwim-shell-commands-join-as-pdf "dwim-shell-commands" "\
+Join all marked images as a single pdf." t)
+(autoload 'dwim-shell-commands-keep-pdf-page "dwim-shell-commands" "\
+Keep a page from pdf." t)
+(autoload 'dwim-shell-commands-join-images-horizontally "dwim-shell-commands" "\
+Join all marked images horizontally as a single image." t)
+(autoload 'dwim-shell-commands-join-images-vertically "dwim-shell-commands" "\
+Join all marked images vertically as a single image." t)
+(autoload 'dwim-shell-commands-image-to-grayscale "dwim-shell-commands" "\
+Convert all marked images to grayscale." t)
+(autoload 'dwim-shell-commands-reorient-image "dwim-shell-commands" "\
+Reorient images." t)
+(autoload 'dwim-shell-commands-gif-to-video "dwim-shell-commands" "\
+Convert all marked gif(s) to video(s)." t)
+(autoload 'dwim-shell-commands-macos-empty-trash "dwim-shell-commands" "\
+Empty macOS trash." t)
+(autoload 'dwim-shell-commands-macos-ocr-text-from-desktop-region "dwim-shell-commands" "\
+Select a macOS desktop area to OCR and copy recognized text to kill ring." t)
+(autoload 'dwim-shell-commands-macos-ocr-text-from-image "dwim-shell-commands" "\
+OCR file and copy recognized text to kill ring." t)
+(autoload 'dwim-shell-commands-macos-convert-to-mp4 "dwim-shell-commands" "\
+Convert to mov to mp4" t)
+(autoload 'dwim-shell-commands-video-to-gif "dwim-shell-commands" "\
+Convert all marked videos to gif(s)." t)
+(autoload 'dwim-shell-commands-video-to-mov "dwim-shell-commands" "\
+Convert all marked videos to mov(s)." t)
+(autoload 'dwim-shell-commands-video-to-webp "dwim-shell-commands" "\
+Convert all marked videos to webp(s)." t)
+(autoload 'dwim-shell-commands-webp-to-video "dwim-shell-commands" "\
+Convert all marked webp(s) to video(s)." t)
+(autoload 'dwim-shell-commands-webp-to-gif "dwim-shell-commands" "\
+Convert all marked webp(s) to gif(s)." t)
+(autoload 'dwim-shell-commands-video-to-hevc-mkv "dwim-shell-commands" "\
+Convert all marked videos to hevc mkv." t)
+(autoload 'dwim-shell-commands-video-to-optimized-gif "dwim-shell-commands" "\
+Convert all marked videos to optimized gif(s)." t)
+(autoload 'dwim-shell-commands-unzip "dwim-shell-commands" "\
+Unzip all marked archives (of any kind) using `atool'." t)
+(autoload 'dwim-shell-commands-zip "dwim-shell-commands" "\
+Zip all marked files into archive.zip." t)
+(autoload 'dwim-shell-commands-zip-password-protect "dwim-shell-commands" "\
+Protect/encrypt zip file(s) with password." t)
+(autoload 'dwim-shell-commands-optimize-gif "dwim-shell-commands" "\
+Convert all marked videos to optimized gif(s)." t)
+(autoload 'dwim-shell-commands-speed-up-gif "dwim-shell-commands" "\
+Speeds up gif(s)." t)
+(autoload 'dwim-shell-commands-image-apply-ios-round-corners "dwim-shell-commands" "\
+Apply iOS round corners to image(s)." t)
+(autoload 'dwim-shell-commands-clip-round-rect-gif "dwim-shell-commands" "\
+Clip gif(s) with round rectangle." t)
+(autoload 'dwim-shell-commands-resize-gif "dwim-shell-commands" "\
+Resize marked gif(s)." t)
+(autoload 'dwim-shell-commands-epub-to-org "dwim-shell-commands" "\
+Convert epub(s) to org." t)
+(autoload 'dwim-shell-commands-docx-to-pdf "dwim-shell-commands" "\
+Convert docx(s) to pdf (via latex)." t)
+(autoload 'dwim-shell-commands-kill-process "dwim-shell-commands" "\
+Select and kill process." t)
+(autoload 'dwim-shell-commands-macos-add-to-photos "dwim-shell-commands" "\
+Add to Photos.app." t)
+(autoload 'dwim-shell-commands-macos-toggle-bluetooth-device-connection "dwim-shell-commands" "\
+Toggle Bluetooth device connection." t)
+(autoload 'dwim-shell-commands-macos-bin-plist-to-xml "dwim-shell-commands" "\
+Convert binary plist to xml." t)
+(autoload 'dwim-shell-commands-macos-toggle-dark-mode "dwim-shell-commands" "\
+Toggle macOS dark mode." t)
+(autoload 'dwim-shell-commands-pdf-to-txt "dwim-shell-commands" "\
+Convert pdf to txt." t)
+(autoload 'dwim-shell-commands-resize-image-by-factor "dwim-shell-commands" "\
+Resize marked image(s) by factor." t)
+(autoload 'dwim-shell-commands-resize-image-in-pixels "dwim-shell-commands" "\
+Resize marked image(s) in pixels." t)
+(autoload 'dwim-shell-commands-pdf-password-protect "dwim-shell-commands" "\
+Add a password to pdf(s)." t)
+(autoload 'dwim-shell-commands-pdf-password-unprotect "dwim-shell-commands" "\
+Remove a password from pdf(s)." t)
+(autoload 'dwim-shell-commands-video-to-mp3 "dwim-shell-commands" "\
+Convert video(s) to mp3." t)
+(autoload 'dwim-shell-commands-video-to-mp3-with-artwork "dwim-shell-commands" "\
+Convert video(s) to mp3 (keep frame as artwork)." t)
+(autoload 'dwim-shell-commands-ndjson-to-org "dwim-shell-commands" "\
+Convert ndjson file to org." t)
+(autoload 'dwim-shell-commands-set-media-artwork-image-metadata "dwim-shell-commands" "\
+Set image artwork metadata for media file(s)." t)
+(autoload 'dwim-shell-commands-video-trim-beginning "dwim-shell-commands" "\
+Drop audio from all marked videos." t)
+(autoload 'dwim-shell-commands-video-trim-end "dwim-shell-commands" "\
+Drop audio from all marked videos." t)
+(autoload 'dwim-shell-commands-video-to-thumbnail "dwim-shell-commands" "\
+Generate a thumbnail for marked video(s)." t)
+(autoload 'dwim-shell-commands-drop-video-audio "dwim-shell-commands" "\
+Drop audio from all marked videos." t)
+(autoload 'dwim-shell-commands-ping-google "dwim-shell-commands" "\
+Ping google.com." t)
+(autoload 'dwim-shell-commands-speed-up-video "dwim-shell-commands" "\
+Speed up video(s)." t)
+(autoload 'dwim-shell-commands-speed-up-video-fragment "dwim-shell-commands" "\
+Speed up fragment in video(s)." t)
+(autoload 'dwim-shell-commands-resize-video "dwim-shell-commands" "\
+Resize marked images." t)
+(autoload 'dwim-shell-commands-clipboard-to-qr "dwim-shell-commands" "\
+Generate a QR code from clipboard." t)
+(autoload 'dwim-shell-commands-sha-256-hash-file-at-clipboard-url "dwim-shell-commands" "\
+Download file at clipboard URL and generate SHA-256 hash." t)
+(autoload 'dwim-shell-commands-view-sqlite-schema-diagram "dwim-shell-commands" "\
+View sqlite schema diagram." t)
+(autoload 'dwim-shell-commands-open-externally "dwim-shell-commands" "\
+Open file(s) externally." t)
+(autoload 'dwim-shell-commands-macos-caffeinate "dwim-shell-commands" "\
+Invoke caffeinate to prevent mac from sleeping." t)
+(autoload 'dwim-shell-commands-macos-make-finder-alias "dwim-shell-commands" "\
+Make macOS Finder alias." t)
+(autoload 'dwim-shell-commands-macos-version-and-hardware-overview-info "dwim-shell-commands" "\
+View macOS version and hardware overview info." t)
+(autoload 'dwim-shell-commands-macos-reveal-in-finder "dwim-shell-commands" "\
+Reveal selected files in macOS Finder." t)
+(autoload 'dwim-shell-commands-macos-share "dwim-shell-commands" "\
+Share selected files from macOS." t)
+(autoload 'dwim-shell-commands-macos-toggle-display-rotation "dwim-shell-commands" "\
+Rotate display." t)
+(autoload 'dwim-shell-commands-make-swift-package-library "dwim-shell-commands" "\
+Create a swift package library" t)
+(autoload 'dwim-shell-commands-make-swift-package-executable "dwim-shell-commands" "\
+Create a swift package executable" t)
+(autoload 'dwim-shell-commands-macos-set-default-app "dwim-shell-commands" "\
+Set default app for file(s)." t)
+(autoload 'dwim-shell-commands-macos-open-with "dwim-shell-commands" "\
+Open file(s) with specific external app." t)
+(autoload 'dwim-shell-commands-macos-open-with-firefox "dwim-shell-commands" "\
+Open file(s) in Firefox." t)
+(autoload 'dwim-shell-commands-macos-open-with-safari "dwim-shell-commands" "\
+Open file(s) in Safari." t)
+(autoload 'dwim-shell-commands-macos-start-recording-window "dwim-shell-commands" "\
+Select and start recording a macOS window." t)
+(autoload 'dwim-shell-commands-macos-end-recording-window "dwim-shell-commands" "\
+Stop recording a macOS window." t)
+(autoload 'dwim-shell-commands-macos-abort-recording-window "dwim-shell-commands" "\
+Stop recording a macOS window." t)
+(autoload 'dwim-shell-commands-macos-screenshot-window "dwim-shell-commands" "\
+Select and screenshot macOS window." t)
+(autoload 'dwim-shell-commands-files-combined-size "dwim-shell-commands" "\
+Get files combined file size." t)
+(autoload 'dwim-shell-commands-image-to-icns "dwim-shell-commands" "\
+Convert png to icns icon." t)
+(autoload 'dwim-shell-commands-image-add-drop-shadow "dwim-shell-commands" "\
+Add a drop shadow." t)
+(autoload 'dwim-shell-commands-image-trim-borders "dwim-shell-commands" "\
+Trim image(s) border (useful for video screenshots)." t)
+(autoload 'dwim-shell-commands-git-clone-clipboard-url-to-downloads "dwim-shell-commands" "\
+Clone git URL in clipboard to \"~/Downloads/\"." t)
+(autoload 'dwim-shell-commands-http-serve-dir "dwim-shell-commands" "\
+HTTP serve current directory." t)
+(autoload 'dwim-shell-commands-git-clone-clipboard-url "dwim-shell-commands" "\
+Clone git URL in clipboard to `default-directory'." t)
+(autoload 'dwim-shell-commands-pass-git-pull "dwim-shell-commands" "\
+Pass git pull." t)
+(autoload 'dwim-shell-commands-git-list-untracked-files "dwim-shell-commands" "\
+List untracked git files in `default-directory'." t)
+(autoload 'dwim-shell-commands-git-delete-untracked-files "dwim-shell-commands" "\
+Delete untracked git files in `default-directory'." t)
+(autoload 'dwim-shell-commands-external-ip "dwim-shell-commands" "\
+Copy external IP to kill ring." t)
+(autoload 'dwim-shell-commands-macos-install-iphone-device-ipa "dwim-shell-commands" "\
+Install iPhone device .ipa.
+Needs ideviceinstaller and libmobiledevice installed." t)
+(autoload 'dwim-shell-commands-copy-to-downloads "dwim-shell-commands" "\
+Copy file to ~/Downloads." t)
+(autoload 'dwim-shell-commands-duplicate "dwim-shell-commands" "\
+Duplicate file." t)
+(autoload 'dwim-shell-commands-rename-all "dwim-shell-commands" "\
+Rename all marked file(s)." t)
+(autoload 'dwim-shell-commands-move-to-downloads "dwim-shell-commands" "\
+Move file to ~/Downloads." t)
+(autoload 'dwim-shell-commands-copy-to-desktop "dwim-shell-commands" "\
+Copy file to ~/Desktop." t)
+(autoload 'dwim-shell-commands-move-to-desktop "dwim-shell-commands" "\
+Move file to ~/Desktop." t)
+(autoload 'dwim-shell-commands-kill-gpg-agent "dwim-shell-commands" "\
+Kill (thus restart) gpg agent.
+
+Useful for when you get this error:
+
+gpg: public key decryption failed: No pinentry
+gpg: decryption failed: No pinentry" t)
+(autoload 'dwim-shell-commands-upload-to-0x0 "dwim-shell-commands" "\
+Upload the marked files to 0x0.st" t)
+(register-definition-prefixes "dwim-shell-commands" '("dwim-shell-commands-"))
+\f
+;;; End of scraped data
+
+(provide 'dwim-shell-command-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; dwim-shell-command-autoloads.el ends here
diff --git a/elpa/dwim-shell-command-20241115.845/dwim-shell-command-pkg.el b/elpa/dwim-shell-command-20241115.845/dwim-shell-command-pkg.el
new file mode 100644 (file)
index 0000000..8c05301
--- /dev/null
@@ -0,0 +1,7 @@
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "dwim-shell-command" "20241115.845"
+  "Shell commands with DWIM behaviour."
+  '((emacs "28.1"))
+  :url "https://github.com/xenodium/dwim-shell-command"
+  :commit "1fa8b9d361f01618bf65111ac0b253adb0599a09"
+  :revdesc "1fa8b9d361f0")
diff --git a/elpa/dwim-shell-command-20241115.845/dwim-shell-command.el b/elpa/dwim-shell-command-20241115.845/dwim-shell-command.el
new file mode 100644 (file)
index 0000000..0e3f8e1
--- /dev/null
@@ -0,0 +1,1072 @@
+;;; dwim-shell-command.el --- Shell commands with DWIM behaviour -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Alvaro Ramirez https://xenodium.com
+
+;; Author: Alvaro Ramirez
+;; Package-Requires: ((emacs "28.1"))
+;; URL: https://github.com/xenodium/dwim-shell-command
+;; Package-Version: 20241115.845
+;; Package-Revision: 1fa8b9d361f0
+
+;; This package 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, or (at your option)
+;; any later version.
+
+;; This package 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.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Provides `dwim-shell-command' as an opinionated DWIM alternative to
+;; `shell-command'.
+;;
+;; Use `dwim-shell-command-on-marked-files' to create your own command
+;; line utilities, invoked via M-x.
+;;
+;; See examples at https://github.com/xenodium/dwim-shell-command/blob/main/dwim-shell-commands.el
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'comint)
+(require 'dired)
+(require 'dired-aux)
+(require 'map)
+(require 'seq)
+(require 'simple)
+(require 'subr-x)
+(require 'view)
+
+(defcustom dwim-shell-command-prompt
+  "DWIM shell command (<<f>> <<fne>> <<e>>): "
+  "`dwim-shell-command' prompt.  Modify if shorter is preferred."
+  :type 'string
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-default-command
+  " '<<f>>'"
+  "Set to nil if no default shell command wanted."
+  :type 'string
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-buffer-name
+  "DWIM shell command"
+  "`dwim-shell-command' buffer name.  Modify if shorter is preferred."
+  :type 'string
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-prompt-on-error nil
+  "If t, prompt user to focus buffer on process error.
+Otherwise, automatically focus buffer on process error."
+  :type 'boolean
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-use-absolute-paths nil
+  "If t, generate absolute paths in templates.  Relative otherwise."
+  :type 'boolean
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-shell-util nil
+  "Shell util, for example: \"zsh\" or \"bash\".
+Set to nil to use `shell-file-name'."
+  :type 'string
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-shell-args nil
+  "Shell util, for example: '(\"-x\" \"-c\").
+Set to nil to use `shell-command-switch'."
+  :type '(repeat string)
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-shell-trace nil
+  "Attempt to add --xtrace to shell command to debug."
+  :type 'boolean
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-done-buffer-name
+  (lambda (name)
+    (format "✅ %s %s" name (propertize "done" 'face 'success)))
+  "Function to format buffer name on success.
+Use `identify' to remove formatting."
+  :type 'function
+  :group 'dwim-shell-command)
+
+(defcustom dwim-shell-command-error-buffer-name
+  (lambda (name)
+    (format "⛔️ %s %s" name (propertize "error" 'face 'error)))
+  "Function to format buffer name on error.
+Use `identify' to remove formatting."
+  :type 'function
+  :group 'dwim-shell-command)
+
+(defvar dwim-shell-command--commands nil "All commands in progress.")
+
+(cl-defstruct
+    dwim-shell-command--command
+  "Describes a command in progress."
+  script
+  process
+  name
+  calling-buffer
+  reporter
+  on-completion
+  files-before
+  silent-success
+  error-autofocus
+  monitor-directory)
+
+;;;###autoload
+(defun dwim-shell-command (prefix)
+  "Execute DWIM shell command asynchronously using noweb templates.
+
+Which files
+
+  `dwim-shell-command' attempts to guess which file(s) you may want
+  the command to operate on.
+
+  1. If visiting a `dired' buffer, draw the marked file(s).
+  2. If visiting any other buffer with an associated file, use that.
+
+Templates
+
+  Operate on drawn files using either the following:
+
+    <<f>> (file path)
+    <<fne>> (file path without extension)
+    <<e>> (extension)
+    <<td>> (generate a temporary directory)
+    <<*>> (all files joined)
+    <<cb>> (clipboard)
+    <<n>>, <<1n>>, or <<An>> (for current iteration)
+
+  For example:
+
+    With drawn files '(\"path/to/image1.png\" \"path/to/image2.png\")
+
+   \"convert '<<f>>' '<<fne>>.jpg'\" expands to
+
+     \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
+     \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
+
+   while \"ls -lh <<*>>\" expands to
+
+     \"ls -lh path/to/image1.png path/to/image2.png\"
+
+Focus
+
+  `dwim-shell-command' creates a process buffer to capture command
+  output, but doesn't display or focus on it by default.  Instead,
+  it tries to guess what's more convenient to focus on.
+
+  While the process is busy, show a spinner in the minibuffer.  No
+  focus changes.
+
+  After process is finished:
+
+  1. If there were any files created in the `default-directory',
+  jump to a `dired' buffer and move point to the new file (via
+  `dired-jump').
+
+  2. If no new files were created, automatically switch focus to the
+  process buffer and display its output.
+
+    Note: You can prevent this automatic focus by prepending your
+    command with whitespace.
+
+      |
+      V
+    \" convert '<<f>>' '<<fne>>.jpg'\"
+
+  3. If the shell command caused any errors, offer to focus the
+  process buffer and display its output.
+
+Quick exit
+
+  Process buffers are read-only and can be quickly closed by
+  pressing `q'.
+
+Prefix
+
+  With PREFIX, execute command that number of times."
+  (interactive "p")
+  (let ((script (dwim-shell-command--read-shell-command)))
+    (dwim-shell-command-on-marked-files
+     dwim-shell-command-buffer-name script
+     :repeat prefix
+     :shell-util dwim-shell-command-shell-util
+     :shell-args dwim-shell-command-shell-args
+     :silent-success (string-prefix-p " " script)
+     :error-autofocus (not dwim-shell-command-prompt-on-error))))
+
+(defun dwim-shell-command--read-shell-command ()
+  "Read a shell command from the minibuffer, using `shell-command-history'."
+  (minibuffer-with-setup-hook
+      (lambda ()
+        (beginning-of-line)
+        (setq-local minibuffer-default-add-function
+                    #'minibuffer-default-add-shell-commands))
+    (read-from-minibuffer dwim-shell-command-prompt dwim-shell-command-default-command nil nil 'shell-command-history)))
+
+(cl-defun dwim-shell-command-on-marked-files (buffer-name script &key utils extensions shell-util shell-args shell-trace shell-pipe post-process-template on-completion repeat silent-success no-progress error-autofocus monitor-directory focus-now join-separator temp-dir)
+  "Create DWIM utilities executing templated SCRIPT on given files.
+
+Here's a simple utility invoking SCRIPT to convert image files to jpg.
+
+  (defun dwim-shell-command-convert-image-to-jpg ()
+    \"Convert all marked images to jpg(s).\"
+    (interactive)
+    (dwim-shell-command-on-marked-files
+     \"Convert to jpg\"
+     \"convert -verbose '<<f>>' '<<fne>>.jpg'\"
+     :utils \"convert\"))
+
+Check `dwim-shell-command-commands.el' for more examples.
+
+All command process output is written to a buffer with BUFFER-NAME.
+
+All params explained in `dwim-shell-command-execute-script'.
+
+Which files
+
+  `dwim-shell-command-on-marked-files' attempts to guess which file(s)
+  you may want the command to operate on.
+
+  1. If visiting a `dired' buffer, draw the marked file(s).
+  2. If visiting any other buffer with an associated file, use that.
+
+Templates
+
+  Operate on drawn files using either the following:
+
+    <<f>> (file path)
+    <<fne>> (file path without extension)
+    <<e>> (extension)
+    <<td>> (generate a temporary directory)
+    <<*>> (all files joined)
+    <<cb>> (clipboard)
+    <<n>>, <<1n>>, or <<An>> (for current iteration)
+
+  For example:
+
+    With drawn files '(\"path/to/image1.png\" \"path/to/image2.png\")
+
+   \"convert '<<f>>' '<<fne>>.jpg'\" expands to
+
+     \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
+     \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
+
+   while \"ls -lh <<*>>\" expands to
+
+     \"ls -lh path/to/image1.png path/to/image2.png\"
+
+Focus
+
+  `dwim-shell-command-on-marked-files' creates a process buffer to
+  capture command output, but doesn't display or focus on it by
+  default.  Instead, it tries to guess what's more convenient to focus
+  on.
+
+  While the process is busy, show a spinner in the minibuffer.  No
+  focus changes.
+
+  After process is finished:
+
+  1. If there were any files created in the `default-directory',
+  jump to a `dired' buffer and move point to the new file (via
+  `dired-jump').
+
+  2. If no new files were created, automatically switch focus to the
+  process buffer and display its output.
+
+    Note: You can prevent this automatic focus by prepending your
+    command with whitespace.
+
+      |
+      V
+    \" convert '<<f>>' '<<fne>>.jpg'\"
+
+  3. If the shell command caused any errors, offer to focus the
+  process buffer and display its output.
+
+Quick exit
+
+  Process buffers are read-only and can be quickly closed by
+  pressing `q'."
+  (dwim-shell-command-execute-script buffer-name script
+                                     :files (dwim-shell-command--files)
+                                     :utils utils
+                                     :extensions extensions
+                                     :shell-util shell-util
+                                     :shell-args shell-args
+                                     :shell-trace shell-trace
+                                     :shell-pipe shell-pipe
+                                     :post-process-template post-process-template
+                                     :on-completion on-completion
+                                     :silent-success silent-success
+                                     :no-progress no-progress
+                                     :repeat repeat
+                                     :error-autofocus error-autofocus
+                                     :monitor-directory monitor-directory
+                                     :focus-now focus-now
+                                     :join-separator join-separator
+                                     :temp-dir temp-dir))
+
+(cl-defun dwim-shell-command-execute-script (buffer-name script &key files extensions shell-util shell-args shell-trace shell-pipe utils post-process-template on-completion silent-success temp-dir repeat no-progress error-autofocus monitor-directory focus-now join-separator)
+  "Execute a script asynchronously, DWIM style with SCRIPT and BUFFER-NAME.
+
+:FILES are used to instantiate SCRIPT as a noweb template.
+
+  The following are supported:
+
+    <<f>> (file path)
+    <<fne>> (file path without extension)
+    <<e>> (extension)
+    <<td>> (generate a temporary directory)
+    <<*>> (all files joined)
+    <<cb>> (clipboard)
+    <<n>>, <<1n>>, or <<An>> (for current iteration)
+
+  For example:
+
+    Given :FILES '(\"path/to/image1.png\" \"path/to/image2.png\")
+
+    \"convert '<<f>>' '<<fne>>.jpg'\" expands to
+
+      \"convert 'path/to/image1.png' 'path/to/image1.jpg'\"
+      \"convert 'path/to/image2.png' 'path/to/image2.jpg'\"
+
+    and \"ls -lh <<*>>\" expands to
+
+      \"ls -lh path/to/image1.png path/to/image2.png\"
+
+:EXTENSIONS ensures that all files in :FILES have the given
+extensions.  Can be either single string \"png\" or a list '(\"png\" \"jpg\").
+
+:SHELL-UTIL and :SHELL-ARGS can be used to specify SCRIPT interpreter.
+
+  For python, use:
+
+    (dwim-shell-command-execute-script
+       \"Print Pi\"
+       \"import math
+         print math.pi\"
+       :shell-util \"python\"
+       :shell-args \"-c\")
+
+:SHELL-PIPE can be used to pipe SCRIPT to it
+
+  For swift, use:
+
+    (dwim-shell-command-on-marked-files
+       \"Print Pi\"
+       \"print(Double.pi)\"
+       :shell-pipe \"swift -\")
+
+:UTILS ensures that all needed command line utilities are installed.
+Can be either a single string \"ffmpeg\" or a list '(\"ffmpet\" \"convert\").
+
+:POST-PROCESS-TEMPLATE enables processing template further after noweb
+instantiation.
+
+:ON-COMPLETION is invoked after SCRIPT executes (disabling DWIM
+internal behavior).
+
+:SILENT-SUCCESS to avoid jumping to process buffer if neither error
+ nor file generated.
+
+:TEMP-DIR to generate a temporary directory for this command.
+This is implied when <<td>> appears in the script.
+
+:REPEAT Use to repeat script N number of times.
+
+:NO-PROGRESS Suppress progress reporting.
+
+:ERROR-AUTOFOCUS Automatically focus process buffer on error.
+
+:MONITOR-DIRECTORY Monitor this directory for new files.
+
+:FOCUS-NOW Immediately focus process buffer once started."
+  (cl-assert buffer-name nil "Script must have a buffer name")
+  (cl-assert (not (string-empty-p script)) nil "Script must not be empty")
+  (when (stringp extensions)
+    (setq extensions (list extensions)))
+  (when (and shell-util (stringp shell-util))
+    (setq shell-util (list shell-util)))
+  (setq shell-util (or shell-util
+                       (when shell-file-name
+                         (list shell-file-name))
+                       '("zsh")))
+  (setq shell-args (or shell-args
+                       (when shell-command-switch
+                         (list shell-command-switch))
+                       '("-x" "-c")))
+  (when (and shell-args (stringp shell-args))
+    (setq shell-args (list shell-args)))
+  ;; See if -x can be prepended.
+  (when (and (not (seq-contains-p shell-args "-x"))
+             (or shell-trace dwim-shell-command-shell-trace)
+             (apply #'dwim-shell-command--program-test
+                    (seq-concatenate
+                     'list shell-util '("-x") shell-args (list "echo"))))
+    (setq shell-args (seq-concatenate 'list '("-x") shell-args)))
+  (when (stringp utils)
+    (setq utils (list utils)))
+  (when (and (string-match-p "\<\<td\>\>" script 0) (not temp-dir))
+    (setq temp-dir (make-temp-file "dwim-shell-command-" t)))
+  (when (and repeat (> repeat 1))
+    (cl-assert (<= (length files) 1) nil
+               "Must not repeat when multiple files are selected.")
+    (setq files (make-list repeat (or (seq-first files) "_no_file_selected_"))))
+  (when (seq-empty-p files)
+    (cl-assert (not (or (dwim-shell-command--contains-multi-file-ref script)
+                        (dwim-shell-command--contains-single-file-ref script)))
+               nil "No files found to expand %s"
+               (or (dwim-shell-command--contains-multi-file-ref script)
+                   (dwim-shell-command--contains-single-file-ref script))))
+  (when extensions
+    (seq-do (lambda (file)
+              (cl-assert (seq-contains-p extensions (downcase (file-name-extension file)))
+                         nil "Not a .%s file" (string-join extensions " .")))
+            files))
+  (seq-do (lambda (util)
+            (cl-assert (executable-find util) nil (format "%s not installed" util)))
+          utils)
+  (let* ((replacements (dwim-shell-command--extract-queries script))
+         (proc-buffer (generate-new-buffer (format "*%s*" buffer-name)))
+         (template script)
+         (script "")
+         (files-before)
+         (proc)
+         (progress-reporter)
+         (padding (dwim-shell-command--digits (length files)))
+         (n (or (dwim-shell-command--n-start-value template padding) "1")))
+    (if (seq-empty-p files)
+        (setq script (dwim-shell-command--expand-file-template template nil post-process-template temp-dir n replacements))
+      (if (dwim-shell-command--contains-multi-file-ref template)
+          (setq script (dwim-shell-command--expand-files-template template files post-process-template temp-dir replacements join-separator))
+        (seq-do (lambda (file)
+                  (setq script
+                        (concat script "\n"
+                                (dwim-shell-command--expand-file-template template file post-process-template temp-dir n replacements)))
+                  (setq n (dwim-shell-command--increment-string n padding)))
+                files)))
+    (setq script (string-trim script))
+    (with-current-buffer proc-buffer
+      (let ((inhibit-message t))
+      ;; Silence noise of entering shell-mode.
+        (comint-mode))
+      (setq default-directory default-directory)
+      (shell-command-save-pos-or-erase)
+      (view-mode +1)
+      (setq view-exit-action 'kill-buffer))
+    (setq files-before (dwim-shell-command--default-directory-files monitor-directory))
+    (setq proc (apply #'start-process (seq-concatenate 'list
+                                                       (list (buffer-name proc-buffer) proc-buffer)
+                                                       shell-util
+                                                       shell-args
+                                                       (if shell-pipe
+                                                           (list (format "echo '%s' | %s" script shell-pipe))
+                                                         (list script)))))
+    (set-process-query-on-exit-flag proc nil)
+    (if no-progress
+        (dwim-shell-command--message "%s started" (process-name proc))
+      (setq progress-reporter (make-progress-reporter
+                               ;; Append space so "done" is spaced when
+                               ;; progress reporter is finished:
+                               ;;
+                               ;; *DWIM shell command* done
+                               (concat (process-name proc) " ")))
+      (progress-reporter-update progress-reporter))
+    ;; Momentarily set buffer to same window, so it's next in recent stack.
+    ;; Makes finding the shell command buffer a lot easier.
+    (let ((current (current-buffer)))
+      (pop-to-buffer-same-window proc-buffer)
+      (pop-to-buffer-same-window current))
+    (when focus-now
+      (switch-to-buffer proc-buffer))
+    (if (equal (process-status proc) 'exit)
+        (dwim-shell-command--finalize (current-buffer)
+                                      files-before
+                                      proc
+                                      progress-reporter
+                                      on-completion
+                                      silent-success
+                                      error-autofocus
+                                      monitor-directory)
+      (setq dwim-shell-command--commands
+            (push (cons (process-name proc)
+                        (make-dwim-shell-command--command :script script
+                                                          :process proc
+                                                          :name (process-name proc)
+                                                          :calling-buffer (current-buffer)
+                                                          :files-before files-before
+                                                          :reporter progress-reporter
+                                                          :on-completion on-completion
+                                                          :silent-success silent-success
+                                                          :error-autofocus error-autofocus
+                                                          :monitor-directory monitor-directory))
+                  dwim-shell-command--commands))
+      (set-process-sentinel proc #'dwim-shell-command--sentinel)
+      (set-process-filter proc #'dwim-shell-command--filter))))
+
+(cl-defun dwim-shell-command-read-file-name (prompt &key extension default)
+  "Invoke `read-string' with PROMPT.
+Validates :EXTENSION and returns :DEFAULT if empty input."
+  (let ((file-name (read-string prompt)))
+    (cond ((string-empty-p (string-trim file-name))
+           default)
+          ((and extension
+                (string-equal (file-name-extension file-name) extension))
+           file-name)
+          ((and extension
+                (not (string-equal (file-name-extension file-name) extension)))
+           (user-error "Name must end in .%s" extension))
+          (t
+           file-name))))
+
+(defun dwim-shell-command--message (message &rest args)
+  "Like `dwim-shell-command--message' but non-blocking.
+MESSAGE and ARGS same as `dwim-shell-command--message'."
+  (let ((message-id (random)))
+    (message (propertize (apply #'format message args) 'message-id message-id))
+    (run-with-timer 3 nil
+                    (lambda ()
+                      (when (and (current-message)
+                                 (eq (get-text-property 0 'message-id (current-message))
+                                     message-id))
+                        (message nil))))))
+
+(defun dwim-shell-command--extract-queries (template)
+  "Extract queries from TEMPLATE.
+
+For all queries, request a value from the user.
+
+For example:
+
+  \"Hello <<Width:100>> world <<Height:200>>\" =>
+
+    ((\"<<Width:100>>\" . \"100\")
+     (\"<<Height:200>>\" . \"200\"))"
+  (let ((matches)
+        (pos 0))
+    (while (and (< pos (length template))
+                (string-match "<<\\([[:alpha:]]\\|[[:blank:]]\\)+:\\([[:alnum:]]\\|[.]\\)*>>" template pos))
+      (setq pos (1+ (match-beginning 0)))
+      (let ((match 0))
+        (push (match-string match template) matches)
+        (setq match (1+ match))))
+    (seq-map (lambda (match)
+               (let* ((query (split-string (string-remove-suffix ">>" (string-remove-prefix "<<" match)) ":"))
+                      (prompt (nth 0 query))
+                      (default-value (if (string-empty-p (nth 1 query))
+                                         nil
+                                       (nth 1 query)))
+                      (value (if (string-match-p "^\\([[:digit:]]\\|[.]\\)+$" default-value)
+                                 (number-to-string (read-number (format "%s: " prompt)
+                                                                (string-to-number default-value)))
+                               (string-trim (read-string (concat prompt
+                                                                 (if default-value
+                                                                     (format " (default %s): " default-value)
+                                                                   ": "))))))
+                      (result (cons match (if (string-empty-p value)
+                                              default-value
+                                            value))))
+                 (cl-assert (cdr result) nil "Must have a value")result))
+             (seq-uniq (nreverse matches)))))
+
+(defun dwim-shell-command--digits (n)
+  "Return the number of digits in N."
+  (let ((count 0))
+    (while (> n 0)
+      (setq n (/ n 10))
+      (setq count (1+ count)))
+    count))
+
+(defun dwim-shell-command--number-to-string (n padding)
+  "Convert N to string using PADDING for number of digits."
+  (format (format "%%0%dd" padding) n))
+
+(defun dwim-shell-command--expand-files-template (template files &optional post-process-template temp-dir replacements join-separator)
+  "Expand TEMPLATE using FILES.
+
+Expand using <<*>> for FILES.
+
+Note: This expander cannot be used to expand <<f>>, <<fne>>, or <<e>>.
+
+  For example:
+
+    Given FILES '(\"path/to/image1.png\" \"path/to/image2.png\")
+
+    \"du -csh '<<*>>'\" expands to
+
+      \"du -csh 'path/to/image1.png' 'path/to/image2.png'\"
+
+Use POST-PROCESS-TEMPLATE to further expand template given own logic.
+
+Set TEMP-DIR to a unique temp directory to this template.
+
+REPLACEMENTS is a cons list of literals to replace with values.
+
+JOIN-SEPARATOR is used to join files from <<*>>."
+  (cl-assert (not (and (dwim-shell-command--contains-multi-file-ref template)
+                       (dwim-shell-command--contains-single-file-ref template)))
+             nil "Must not have %s and %s in the same template"
+             (dwim-shell-command--contains-multi-file-ref template)
+             (dwim-shell-command--contains-single-file-ref template))
+  (setq files (seq-map (lambda (file)
+                         (if dwim-shell-command-use-absolute-paths
+                             (expand-file-name file)
+                           (file-relative-name (expand-file-name file) default-directory)))
+                       files))
+
+  (mapc (lambda (replacement)
+          (setq template
+                (string-replace (car replacement) (cdr replacement) template)))
+        replacements)
+
+  ;; Try to use quotes surrounding <<*>> in each path.
+  ;; "'<<*>>'" with '("path/to/image1.png" "path/to/image2.png") -> "'path/to/image1.png' 'path/to/image2.png'"
+  (when-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<\\*\>\>" template))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+    (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<\\*\>\>\\)\\([^ ]\\)"
+                                             (string-join (seq-map (lambda (file)
+                                                                     (concat unescaped-quote
+                                                                             (string-replace unescaped-quote
+                                                                                             escaped-quote file) unescaped-quote))
+                                                                   files)
+                                                          (or join-separator " "))
+                                             template nil nil 0)))
+
+  ;; "<<some.txt(u)>>" -> some.txt (if unique)
+  ;;                   -> some(1).txt (if it exist)
+  (when-let* ((found (string-match "\<\<\\([^ ]?+\\)(u)\>\>" template))
+              (name (match-string 1 template)))
+    (setq template (replace-regexp-in-string "\<\<\\([^ ]?+\\)(u)\>\>"
+                                             (dwim-shell-command--unique-new-file-path name)
+                                             template nil nil 0)))
+
+  ;; "<<~>>" -> "/home/user" (or equivalent).
+  (when (string-match "\<\<~\>\>" template)
+    (setq template (replace-regexp-in-string "\<\<~\>\>"
+                                             (expand-file-name "~")
+                                             template nil nil 0)))
+
+  ;; "<<*>>" with '("path/to/image1.png" "path/to/image2.png") -> "path/to/image1.png path/to/image2.png"
+  (setq template (replace-regexp-in-string "\\(\<\<\\*\>\>\\)" (string-join files (or join-separator " ")) template nil nil 1))
+
+  ;; "<<td>>" with TEMP-DIR -> "/var/folders/m7/ky091cp56d5g68nyhl4y7frc0000gn/T/dwim-shell-command-JNK4V5"
+  (setq template (replace-regexp-in-string "\\(\<\<td\>\>\\)" temp-dir template nil nil 1))
+
+  ;; "<<cb>>" with (current-kill 0) -> "whatever was in kill ring"
+  (when (string-match "\<\<cb\>\>" template)
+    (setq template (replace-regexp-in-string "\\(\<\<cb\>\>\\)" (current-kill 0)
+                                             template nil nil 1)))
+
+  (when post-process-template
+    (setq template (funcall post-process-template template files)))
+  template)
+
+(defun dwim-shell-command--escaped-quote-around (needle haystack &optional unbalanced)
+  "Find NEEDLE in HAYSTACK that's surrounded by either ' or \".
+
+Set UNBALANCED to t if NEEDLE isn't surrounded by quotes on both sides.
+
+For example:
+
+ \"\<\<fne\>\>\" \"before \"<<fne>>\" after\" => (\"\"\" \"\\\\\"\")
+
+ \"\<\<fne\>\>\" \"before '<<fne>>' after\" => (\"'\" \"'\"'\"'\")"
+  (when-let ((found (string-match (format "\\([^ ]\\)\\(%s\\)\\([^ ]\\)" needle) haystack))
+             (unescaped-quote (if unbalanced
+                                  (or (match-string 1 haystack)
+                                      (match-string 3 haystack))
+                                (cl-assert (string-equal (match-string 1 haystack)
+                                                         (match-string 3 haystack)) nil
+                                                         "%s must match %s"
+                                                         (match-string 1 haystack)
+                                                         (match-string 3 haystack))
+                                (match-string 1 haystack)))
+             (escaped-quote "'"))
+    ;; Known quoted quotes.
+    (cond
+     ((string-equal unescaped-quote "\"")
+      (setq escaped-quote "\\\\\""))
+     ((string-equal unescaped-quote "'")
+      (setq escaped-quote "'\"'\"'"))
+     ;; Ignore slashes as user may be joining paths.
+     ((string-equal unescaped-quote "/")
+      (setq escaped-quote "/"))
+     (t
+      (error "Couldn't figure out how to quote for \"%s\" using %s and %s"
+             haystack
+             (match-string 1 haystack)
+             (match-string 3 haystack))))
+    (list unescaped-quote escaped-quote)))
+
+(defun dwim-shell-command--expand-file-template (template file &optional post-process-template temp-dir current replacements)
+  "Expand TEMPLATE using FILE.
+
+Expand using <<f>> for FILE, <<fne>> for FILE without extension, and
+<<e>> for FILE extension.  <<n>>, <<1n>>, or <<an>> is replaced with
+CURRENT.  <<some.txt(u)>> expands to unique \"some(1).txt\".
+
+Note: This expander cannot be used to expand <<*>>.
+
+  For example:
+
+    Given FILE \"path/to/image.png\"
+
+    \"convert '<<f>>' '<<fne>>.jpg'\" expands to
+
+      \"convert 'path/to/image.png' 'path/to/image.jpg'\"
+
+Use POST-PROCESS-TEMPLATE to further expand template given own logic.
+
+Set TEMP-DIR to a unique temp directory to this template.
+
+REPLACEMENTS is a cons list of literals to replace with values."
+  (cl-assert (not (and (dwim-shell-command--contains-multi-file-ref template)
+                       (dwim-shell-command--contains-single-file-ref template)))
+             nil "Must not have %s and %s in the same template"
+             (dwim-shell-command--contains-multi-file-ref template)
+             (dwim-shell-command--contains-single-file-ref template))
+
+  (mapc (lambda (replacement)
+          (setq template
+                (string-replace (car replacement) (cdr replacement) template)))
+        replacements)
+
+  (when file
+    (setq file (if dwim-shell-command-use-absolute-paths
+                   (expand-file-name file)
+                 (file-relative-name (expand-file-name file) default-directory)))
+    ;; "<<fne>>" with "/path/tmp.txt" -> "/path/tmp"
+    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<fne\>\>" template t))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<fne\>\>\\)"
+                                                 (string-replace unescaped-quote escaped-quote (file-name-sans-extension file))
+                                                 template nil nil 2))
+      (setq template (replace-regexp-in-string "\\(\<\<fne\>\>\\)" (file-name-sans-extension file) template nil nil 1)))
+
+    ;; "<<b>>" with "/path/tmp.txt" -> "tmp.txt"
+    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<b\>\>" template t))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+        (setq template (replace-regexp-in-string "\\(\<\<b\>\>\\)\\([^ ]\\)"
+                                                 (string-replace unescaped-quote escaped-quote (file-name-nondirectory file))
+                                                 template nil nil 1))
+      (setq template (replace-regexp-in-string "\\(\<\<b\>\>\\)" (file-name-nondirectory file) template nil nil 1)))
+
+    ;; "<<bne>>" with "/path/tmp.txt" -> "tmp"
+    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<bne\>\>" template t))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+        (setq template (replace-regexp-in-string "\\(\<\<bne\>\>\\)\\([^ ]\\)"
+                                                 (string-replace unescaped-quote escaped-quote
+                                                                 (file-name-sans-extension (file-name-nondirectory file)))
+                                                 template nil nil 1))
+      (setq template (replace-regexp-in-string "\\(\<\<bne\>\>\\)" (file-name-sans-extension
+                                                                (file-name-nondirectory file)) template nil nil 1)))
+
+    ;; "<<e>>" with "/path/tmp.txt" -> "txt"
+    (if (file-name-extension file)
+        (setq template (replace-regexp-in-string "\\(\<\<e\>\>\\)" (file-name-extension file) template nil nil 1))
+      ;; File had no extension. Attempt to remove .<<e>>.
+      (setq template (replace-regexp-in-string "\\(\.\<\<e\>\>\\)" "" template nil nil 1)))
+
+    ;; "<<f>>" with "/path/file.jpg" -> "/path/file.jpg"
+    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<f\>\>" template))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<f\>\>\\)\\([^ ]\\)"
+                                                 (string-replace unescaped-quote escaped-quote file)
+                                                 template nil nil 2))
+      (setq template (replace-regexp-in-string "\\(\<\<f\>\>\\)" file template nil nil 1)))
+
+    ;; "<<f(u)>>" with "/path/file.jpg" -> "/path/file(1).jpg"
+    (if-let* ((quoting (dwim-shell-command--escaped-quote-around "\<\<f(u)\>\>" template))
+              (unescaped-quote (nth 0 quoting))
+              (escaped-quote (nth 1 quoting)))
+        (setq template (replace-regexp-in-string "\\([^ ]\\)\\(\<\<f\(u)>\>\\)\\([^ ]\\)"
+                                                 (string-replace unescaped-quote escaped-quote
+                                                                 (dwim-shell-command--unique-new-file-path file))
+                                                 template nil nil 2))
+      (setq template (replace-regexp-in-string "\\(\<\<f(u)\>\>\\)"
+                                               (dwim-shell-command--unique-new-file-path file) template nil nil 1))))
+
+  ;; "<<some.txt(u)>>" -> some.txt (if unique)
+  ;;                   -> some(1).txt (if it exist)
+  (when-let* ((found (string-match "\<\<\\([^ ]?+\\)(u)\>\>" template))
+              (name (match-string 1 template)))
+    (setq template (replace-regexp-in-string "\<\<\\([^ ]?+\\)(u)\>\>"
+                                             (dwim-shell-command--unique-new-file-path name)
+                                             template nil nil 0)))
+
+  ;; "<<~>>" -> "/home/user" (or equivalent).
+  (when (string-match "\<\<~\>\>" template)
+    (setq template (replace-regexp-in-string "\<\<~\>\>"
+                                             (expand-file-name "~")
+                                             template nil nil 0)))
+
+  ;; "<<td>>" with TEMP-DIR -> "/var/folders/m7/ky091cp56d5g68nyhl4y7frc0000gn/T/dwim-shell-command-JNK4V5"
+  (setq template (replace-regexp-in-string "\\(\<\<td\>\>\\)" temp-dir template nil nil 1))
+
+  ;; "<<cb>>" with (current-kill 0) -> "whatever was in kill ring"
+  (when (string-match "\<\<cb\>\>" template)
+    (setq template (replace-regexp-in-string "\\(\<\<cb\>\>\\)" (current-kill 0)
+                                             template nil nil 1)))
+
+  ;; "<<n>>" or "<<an>" or "<<1n>" with current.
+  (setq template (replace-regexp-in-string "\\(\<\<[[:alnum:]]?+n\>\>\\)" current
+                                           template nil nil 1))
+
+  (when post-process-template
+    (setq template (funcall post-process-template template file)))
+  template)
+
+(defun dwim-shell-command--contains-single-file-ref (template)
+  "Check for <<f>>, <<fne>>, or <<e>> in TEMPLATE."
+  (cond ((string-match "\<\<f\>\>" template)
+         "<<f>>")
+        ((string-match "\<\<fne\>\>" template)
+         "<<fne>>")
+        ((string-match "\<\<b\>\>" template)
+         "<<b>>")
+        ((string-match "\<\<bne\>\>" template)
+         "<<bne>>")
+        ((string-match "\<\<e\>\>" template)
+         "<<e>>")))
+
+(defun dwim-shell-command--contains-multi-file-ref (template)
+  "Check for <<*>> in TEMPLATE."
+  (when (string-match "\<\<\\*\>\>" template)
+    "<<*>>"))
+
+(defun dwim-shell-command--default-directory-files (override)
+  "List of files in current buffer's `default-directory'.
+Use OVERRIDE to override `default-directory'."
+  (when-let ((default-directory (or override default-directory)))
+    (seq-map (lambda (filename)
+               (file-name-concat default-directory filename))
+             (cl-remove-if
+              (lambda (e) (member e '("." "..")))
+              (directory-files default-directory)))))
+
+(defun dwim-shell-command--last-modified-between (before after)
+  "Compare files in BEFORE and AFTER and return oldest file in diff."
+  (car (last (seq-sort #'file-newer-than-file-p
+                       (seq-difference after before)))))
+
+(defun dwim-shell-command--finalize (calling-buffer files-before process progress-reporter on-completion silent-success error-autofocus monitor-directory)
+  "Finalize script execution.
+
+CALLING-BUFFER, FILES-BEFORE, PROCESS, PROGRESS-REPORTER,
+ERROR-AUTOFOCUS, ON-COMPLETION, SILENT-SUCCESS, and MONITOR-DIRECTORY are
+all needed to finalize processing."
+  (let ((oldest-new-file)
+        (files-after))
+    (when progress-reporter
+      (progress-reporter-done progress-reporter))
+    (if (= (process-exit-status process) 0)
+        (progn
+          (dwim-shell-command--message (funcall dwim-shell-command-done-buffer-name (process-name process)))
+          (with-current-buffer (process-buffer process)
+            (rename-buffer (generate-new-buffer-name (funcall dwim-shell-command-done-buffer-name (process-name process)))))
+          (if on-completion
+              (funcall on-completion (process-buffer process) process)
+            (with-current-buffer calling-buffer
+              (if (equal major-mode 'dired-mode)
+                  (progn (when revert-buffer-function
+                           (funcall revert-buffer-function nil t))
+                         ;; Region is not accurate if new files added. Wipe it.
+                         (when (use-region-p)
+                           (deactivate-mark)))
+                (when (and (or buffer-auto-save-file-name
+                               buffer-file-name)
+                           (not (verify-visited-file-modtime)))
+                  ;; Already visiting a file. Revert if modified by command.
+                  (revert-buffer :ignore-auto :noconfirm)))
+              (with-current-buffer (process-buffer process)
+                (setq files-after (dwim-shell-command--default-directory-files monitor-directory)))
+              (setq oldest-new-file
+                    (dwim-shell-command--last-modified-between
+                     files-before
+                     files-after))
+              ;; There's at least one new file. Show that.
+              (if oldest-new-file
+                  (dired-jump nil oldest-new-file)
+                ;; Files may have been deleted but harder to track.
+                ;; Open dired and refresh to show files are gone.
+                (unless (equal (length files-after)
+                               (length files-before))
+                  (dired monitor-directory)
+                  (when revert-buffer-function
+                    (funcall revert-buffer-function nil t)))))
+            (unless (equal (process-buffer process)
+                           (window-buffer (selected-window)))
+              (if (or oldest-new-file silent-success)
+                  (kill-buffer (process-buffer process))
+                (unless silent-success
+                  (switch-to-buffer (process-buffer process)))))))
+      (if on-completion
+          (funcall on-completion (process-buffer process) process)
+        (if (and (buffer-name (process-buffer process))
+                 (or error-autofocus
+                     ;; Buffer already selected. Don't ask.
+                     (equal (process-buffer process)
+                            (window-buffer (selected-window)))
+                     (ignore-error quit
+                       (y-or-n-p (format "%s error, see output? "
+                                         (buffer-name (process-buffer process)))))))
+            (progn
+              (with-current-buffer (process-buffer process)
+                (rename-buffer (generate-new-buffer-name (funcall dwim-shell-command-error-buffer-name (process-name process)))))
+              (when (or error-autofocus
+                        (equal (process-buffer process)
+                               (window-buffer (selected-window))))
+                (dwim-shell-command--message (funcall dwim-shell-command-error-buffer-name (process-name process))))
+              (switch-to-buffer (process-buffer process)))
+          (kill-buffer (process-buffer process)))))
+    (setq dwim-shell-command--commands
+          (map-delete dwim-shell-command--commands (process-name process)))))
+
+(defun dwim-shell-command--unique-new-file-path (file-path)
+  "Return a unique FILE-PATH.
+
+If FILE-PATH already contains a number in the format (n), set counter to n.
+
+\"/tmp/blah.txt\" -> \"/tmp/blah(1).txt\"
+\"/tmp/blah(2).txt\" -> \"/tmp/blah(3).txt\""
+  (let* ((name (file-name-sans-extension file-path))
+         (extension (file-name-extension file-path))
+         (counter (if (string-match "(\\([0-9]+\\))$" name)
+                      (string-to-number (match-string 1 name))
+                    0)))
+    (when (string-match "(\\([0-9]+\\))$" name)
+      (setq name(replace-match "" t t name)))
+    (while (file-exists-p file-path)
+      (setq counter (1+ counter))
+      (setq file-path (if extension
+                          (format "%s(%d).%s" name counter extension)
+                        (format "%s(%d)" name counter))))
+    file-path))
+
+(defun dwim-shell-command--sentinel (process _)
+  "Handles PROCESS sentinel and STATE."
+  (let ((exec (map-elt dwim-shell-command--commands (process-name process))))
+    (dwim-shell-command--finalize (dwim-shell-command--command-calling-buffer exec)
+                                  (dwim-shell-command--command-files-before exec)
+                                  process
+                                  (dwim-shell-command--command-reporter exec)
+                                  (dwim-shell-command--command-on-completion exec)
+                                  (dwim-shell-command--command-silent-success exec)
+                                  (dwim-shell-command--command-error-autofocus exec)
+                                  (dwim-shell-command--command-monitor-directory exec))))
+
+(defun dwim-shell-command--filter (process output)
+  "Handles PROCESS filtering and STATE and OUTPUT."
+  (when-let* ((exec (map-elt dwim-shell-command--commands (process-name process)))
+              (reporter (dwim-shell-command--command-reporter exec)))
+    (progress-reporter-update reporter))
+  (comint-output-filter process output))
+
+(defun dwim-shell-command--file-extensions ()
+  "Return buffer file extension or marked/region extensions for a `dired' buffer."
+  (seq-uniq
+   (seq-map
+    #'file-name-extension
+    (seq-filter
+     #'file-name-extension
+     (dwim-shell-command--files)))))
+
+(defun dwim-shell-command--files ()
+  "Return buffer file (if available) or marked/region files for a `dired' buffer."
+  (cl-assert (not (and (use-region-p) (let ((files (dired-get-marked-files nil nil nil t)))
+                                     ;; Based on `dired-number-of-marked-files'.
+                                     (cond ((null (cdr files))
+                                            nil)
+                                           ((and (= (length files) 2)
+                                                 (eq (car files) t))
+                                            t)
+                                           (t
+                                            (not (seq-empty-p files)))))))
+             nil "Region and marked files both active. Choose one only.")
+  (if (buffer-file-name)
+      (list (buffer-file-name))
+    (or
+     (dwim-shell-command--dired-paths-in-region)
+     (dired-get-marked-files))))
+
+(defun dwim-shell-command--dired-paths-in-region ()
+  "If `dired' buffer, return region files.  nil otherwise."
+  (when (and (equal major-mode 'dired-mode)
+             (use-region-p))
+    (let ((start (region-beginning))
+          (end (region-end))
+          (paths))
+      (save-excursion
+        (save-restriction
+          (goto-char start)
+          (while (< (point) end)
+            ;; Skip non-file lines.
+            (while (and (< (point) end) (dired-between-files))
+              (forward-line 1))
+            (when (dired-get-filename nil t)
+              (setq paths (append paths (list (dired-get-filename nil t)))))
+            (forward-line 1))))
+      paths)))
+
+(defun dwim-shell-command--n-start-value (template padding)
+  "Extract n start value from TEMPLATE using PADDING.
+Falls back to \"1\"."
+  (when (string-match "\<\<\\([[:alnum:]]?+\\)n\>\>" template)
+    (if (string-empty-p (match-string 1 template))
+        (dwim-shell-command--increment-string (number-to-string 0) padding)
+      (if-let* ((start-string (match-string 1 template))
+                (start-n (string-to-number start-string)))
+          (dwim-shell-command--increment-string (number-to-string (1- start-n)) padding)
+        (match-string 1 template)))))
+
+(defun dwim-shell-command--increment-string (text padding)
+  "Increment TEXT using PADDING.
+\"a\" -> \"b\"
+\"1\" -> \"2\""
+  (cond ((string-match "^[[:alpha:]]$" text) ;; char
+         (char-to-string (1+ (string-to-char (match-string 0 text)))))
+        ((string-match "^[[:digit:]]+$" text) ;; char
+         (dwim-shell-command--number-to-string (1+ (string-to-number (match-string 0 text))) padding))))
+
+(defun dwim-shell-command--program-test (program &rest args)
+  "Test that running PROGRAM with ARGS is successful."
+  (eq 0 (apply #'call-process program nil nil nil args)))
+
+(cl-defun dwim-shell-command-foreach (fun &key monitor-directory)
+  "Execute FUN for each file.
+Monitor :MONITOR-DIRECTORY for new file and `dired-jump' to it."
+  (let ((files (dwim-shell-command--files))
+        (files-before (dwim-shell-command--default-directory-files  monitor-directory))
+        (oldest-new-file)
+        (created-file)
+        (jump-to))
+    (mapc (lambda (file-path)
+            (setq created-file (or (funcall fun file-path)
+                                   created-file)))
+          files)
+    (setq oldest-new-file (dwim-shell-command--last-modified-between
+                           files-before
+                           (dwim-shell-command--default-directory-files monitor-directory)))
+    (setq jump-to (or oldest-new-file created-file))
+    (when jump-to
+      (dired-jump nil jump-to))))
+
+(provide 'dwim-shell-command)
+
+;;; dwim-shell-command.el ends here
diff --git a/elpa/dwim-shell-command-20241115.845/dwim-shell-commands.el b/elpa/dwim-shell-command-20241115.845/dwim-shell-commands.el
new file mode 100644 (file)
index 0000000..7e981ff
--- /dev/null
@@ -0,0 +1,1722 @@
+;;; dwim-shell-commands.el --- Useful commands -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Alvaro Ramirez
+
+;; Author: Alvaro Ramirez
+;; URL: https://github.com/xenodium/dwim-shell-command
+
+;; This package 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, or (at your option)
+;; any later version.
+
+;; This package 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.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A collection of useful commands created via
+;; `dwim-shell-command-on-marked-files'.
+
+;;; Code:
+
+(require 'browse-url)
+(require 'cl-lib)
+(require 'dwim-shell-command)
+(require 'files)
+(require 'proced)
+(require 'seq)
+(require 'subr-x)
+
+;;;###autoload
+(defun dwim-shell-commands-audio-to-mp3 ()
+  "Convert all marked audio to mp3(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to mp3"
+   "ffmpeg -stats -n -i '<<f>>' -acodec libmp3lame '<<fne>>.mp3'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-extract-har-content (prefix)
+  "Extract all har content fields to files."
+  (interactive "P")
+  (when prefix
+    (setq prefix (string-trim (read-string "Transcription locale: " "ja-JP")))
+    (when (string-empty-p prefix)
+      (errro "No locale given")))
+  (dwim-shell-command-on-marked-files
+   "Extract har response content"
+   (format "declare -A mime_map=( \
+  [\"audio/mpeg\"]=\"mp3\" \
+  [\"image/jpeg\"]=\"jpg\" \
+  [\"text/plain\"]=\"txt\" \
+  [\"application/json\"]=\"json\" \
+  # TODO: Add more mappings if needed.
+)
+outdir='<<fne>>'
+mkdir -p \"${outdir}\"
+  : > \"${outdir}/<<bne>>.org\"
+jq -r '.log.entries[] | @base64' '<<f>>' | while read -r entry; do
+  url=$(echo \"$entry\" | base64 --decode | jq -r '.request.url')
+  basename=$(echo \"$entry\" | base64 --decode | jq -r '.request.url | capture(\"(?<=//)[^/]+/(?<path>.*)\") | .path | gsub(\"[^a-zA-Z0-9]\"; \"_\")')
+  mime=$(echo \"$entry\" | base64 --decode | jq -r '.response.content.mimeType')
+  extension=${mime_map[$mime]:-\"bin\"}
+  name=\"${outdir}/${basename:0:255}.${extension}\"
+  echo \"${name}\"
+  content=$(echo \"$entry\" | base64 --decode | jq -r '.response.content.text')
+  echo \"$content\" | base64 --decode > \"${name}\"
+  if [ -f \"${name}\" ] && %s; then
+    transcription=$(macosrec --speech-to-text --locale %s --input \"${name}\")
+    if [ $? -eq 0 ]; then
+      transcribed_name=\"${outdir}/${transcription}.${extension}\"
+      mv \"${name}\" \"${transcribed_name}\"
+      basename=$(basename \"${transcribed_name}\")
+      echo \"[[file:${basename}][${transcription}]] [[${url}][remote]]\" >> \"${outdir}/<<bne>>.org\"
+    fi
+  fi
+done"
+           (if prefix
+               "true"
+             "false")
+           prefix
+           (if prefix
+               "true"
+             "false"))
+   :utils "jq"
+   :extensions "har"))
+
+;;;###autoload
+(defun dwim-shell-extract-har-urls ()
+  "Get all request URLs."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Extract har request URLs"
+   "jq -r '.log.entries[].request.url' '<<f>>'"
+   :utils "jq"))
+
+;;;###autoload
+(defun dwim-shell-view-open-ports-per-app ()
+  "View open ports per app"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Ports per app"
+   ;; https://x.com/nurmiwilliam/status/1823228630664634695
+   "sudo lsof -iTCP -sTCP:LISTEN -n -P | awk 'NR>1 {print $9, $1, $2}' | sed 's/.*://' | sort -u | while read port process pid; do echo \"Port $port: $(ps -p $pid -o command= | sed 's/^-//') (PID: $pid)\"; done | sort -n"
+   :utils "jq"))
+
+;;;###autoload
+(defun dwim-shell-commands-open-clipboard-url ()
+  "Open clipboard URL.  Offer to stream if possible."
+  (interactive)
+  (let ((url (or (current-kill 0)
+                 (user-error "Nothing in clipboard"))))
+    (dwim-shell-commands-url-browse url)))
+
+(defun dwim-shell-commands-url-browse (url &rest args)
+  "If URL is playable media, offer to open in mpv.  Else browser.
+Optional argument ARGS as per `browse-url-default-browser'"
+  (if (and (or (string-match-p "^http[s]?://.*youtube.com" url)
+               (string-match-p "^http[s]?://.*m.youtube.com" url)
+               (string-match-p "^http[s]?://.*youtu.be" url)
+               (string-match-p "^http[s]?://.*soundcloud.com" url)
+               (string-match-p "^http[s]?://.*redditmedia.com" url)
+               (string-match-p "^http[s]?://.*reddit.com" url)
+               (string-match-p "^http[s]?://.*bandcamp.com" url))
+           (y-or-n-p "Stream from mpv? "))
+      (dwim-shell-command-on-marked-files
+       "Streaming"
+       (format "mpv --geometry=30%%x30%%+100%%+0%% '%s'" url)
+       :utils "mpv"
+       :no-progress t
+       :error-autofocus t
+       :silent-success t)
+    (funcall #'browse-url-default-browser url args)))
+
+;;;###autoload
+(defun dwim-shell-commands-stream-clipboard-url ()
+  "Stream clipboard URL using mpv."
+  (interactive)
+  (cl-assert (string-match-p "^http[s]?://" (current-kill 0)) nil "Not a URL")
+  (dwim-shell-command-on-marked-files
+   "Streaming"
+   "mpv --geometry=30%x30%+100%+0% \"<<cb>>\""
+   :utils "mpv"
+   :no-progress t
+   :error-autofocus t
+   :silent-success t))
+
+;;;###autoload
+(defun dwim-shell-commands-download-clipboard-stream-url ()
+  "Download clipboard URL."
+  (interactive)
+  (cl-assert (string-match-p "^http[s]?://" (current-kill 0)) nil "Not a URL")
+  (dwim-shell-command-on-marked-files
+   "Downloading"
+   "youtube-dl --newline -o \"~/Downloads/%(title)s.%(ext)s\" \"<<cb>>\""
+   :utils "youtube-dl"
+   :no-progress t
+   :error-autofocus t
+   :monitor-directory "~/Downloads"
+   :silent-success t))
+
+;;;###autoload
+(defun dwim-shell-commands-image-clear-exif-metadata ()
+  "Clear EXIF metadata in image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "View EXIF"
+   "cp '<<f>>' '<<fne>>_cleared.<<e>>'
+    exiftool -all:all= -overwrite_original '<<fne>>_cleared.<<e>>'"
+   :utils "exiftool"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-scan-code ()
+  "Scan any code (ie. qr, bar, etc) from image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Scan code"
+   "zbarimg --quiet '<<f>>'"
+   :utils "zbarimg"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-exif-metadata ()
+  "View EXIF metadata in image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "View EXIF"
+   "exiftool '<<f>>'"
+   :utils "exiftool"))
+
+;;;###autoload
+(defun dwim-shell-commands-tesseract-ocr-text-from-image ()
+  "Extract text from image via tesseract."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Extract text from image via tesseract."
+   "tesseract '<<f>>' -"
+   :utils "tesseract"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-view-location-in-openstreetmap ()
+  "Open image(s) location in map/browser."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Browse location"
+   "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\"
+    if [ -z \"$lat\" ]; then
+      echo \"no latitude\"
+      exit 1
+    fi
+    lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\"
+    if [ -z \"$lon\" ]; then
+      echo \"no longitude\"
+      exit 1
+    fi
+    if [[ $OSTYPE == darwin* ]]; then
+      open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\"
+    else
+      xdg-open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\"
+    fi"
+   :utils "exiftool"
+   :error-autofocus t
+   :silent-success t))
+
+;;;###autoload
+(defun dwim-shell-commands-image-reverse-geocode-location ()
+  "Reverse geocode image(s) location."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Reverse geocode"
+   "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\"
+    if [ -z \"$lat\" ]; then
+      echo \"no latitude\"
+      exit 1
+    fi
+    lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\"
+    if [ -z \"$lon\" ]; then
+      echo \"no longitude\"
+      exit 1
+    fi
+    json=$(curl \"https://nominatim.openstreetmap.org/reverse?format=json&accept-language=en&lat=${lat}&lon=${lon}&zoom=18&addressdetails=1\")
+    echo \"json_start $json json_end\""
+   :utils '("exiftool" "curl")
+   :silent-success t
+   :error-autofocus t
+   :on-completion (lambda (buffer _process)
+                    (with-current-buffer buffer
+                      (goto-char (point-min))
+                      (let ((matches '()))
+                        (while (re-search-forward "^json_start\\(.*?\\)json_end" nil t)
+                          (push (match-string 1) matches))
+                        (message "%s" (string-join (seq-map (lambda (json)
+                                                              (map-elt (json-parse-string json :object-type 'alist) 'display_name))
+                                                            matches)
+                                                   "\n")))
+                      (kill-buffer buffer)))))
+
+;;;###autoload
+(defun dwim-shell-commands-image-horizontal-flip ()
+  "Horizontally flip image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Image horizontal flip"
+   "convert -verbose -flop '<<f>>' '<<fne>>_h_flipped.<<e>>'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-vertical-flip ()
+  "Horizontally flip image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Image vertical flip"
+   "convert -verbose -flip '<<f>>' '<<fne>>_v_flipped.<<e>>'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-to-jpg ()
+  "Convert all marked images to jpg(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to jpg"
+   "convert -verbose '<<f>>' '<<fne>>.jpg'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-to-png ()
+  "Convert all marked images to png(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to png"
+   "convert -verbose '<<f>>' '<<fne>>.png'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-svg-to-png ()
+  "Convert all marked svg(s) to png(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to png"
+   "rsvg-convert -b white '<<f>>' -f png -o '<<fne>>.png'"
+   :utils "rsvg-convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-svg-to-favicons ()
+  "Convert svg to common favicons."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Create favicons"
+   "echo '<link rel=\"icon\" type=\"image/svg+xml\" href=\"<<f>>\">'
+   rsvg-convert -o '<<fne>>-16.png' -w 16 -h 16 '<<f>>'
+   echo '<link rel=\"icon\" type=\"image/png\" href=\"<<fne>>-16.png\" sizes=\"16x16\">'
+   rsvg-convert -o '<<fne>>-32.png' -w 32 -h 32 '<<f>>'
+   echo '<link rel=\"icon\" type=\"image/png\" href=\"<<fne>>-32.png\" sizes=\"32x32\">'
+   rsvg-convert -o '<<fne>>-48.png' -w 48 -h 48 '<<f>>'
+   echo '<link rel=\"icon\" type=\"image/png\" href=\"<<fne>>-48.png\" sizes=\"48x48\">'
+   convert '<<fne>>-16.png' '<<fne>>-32.png' '<<fne>>-48.png' '<<fne>>.ico'
+   echo '<link rel=\"icon\" type=\"image/x-icon\" href=\"/'<<fne>>.ico'\">'
+   rsvg-convert -o '<<fne>>-64.png' -w 64 -h 64 '<<f>>'
+   echo '<link rel=\"icon\" type=\"image/png\" href=\"<<fne>>-64.png\" sizes=\"64x64\">'
+   rsvg-convert -o '<<fne>>-180.png' -w 180 -h 180 '<<f>>'
+   echo '<link rel=\"apple-touch-icon\" href=\"<<fne>>-180.png\" sizes=\"180x180\">'
+   read -n 1 -s -r"
+   :extensions "svg"
+   :utils '("convert" "rsvg-convert")
+   :focus-now t))
+
+;;;###autoload
+(defun dwim-shell-commands-make-transparent-png ()
+  "Create a transparent png."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Create transparent png"
+   "convert -verbose -size <<width:200>>x<<height:200>> xc:none '<<empty<<width:200>>x<<height:200>>.png(u)>>'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-join-as-pdf ()
+  "Join all marked images as a single pdf."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Join as pdf"
+   (format "convert -verbose '<<*>>' '<<%s(u)>>'"
+           (dwim-shell-command-read-file-name
+            "Join as pdf named (default \"joined.pdf\"): "
+            :extension "pdf"
+            :default "joined.pdf"))
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-keep-pdf-page ()
+  "Keep a page from pdf."
+  (interactive)
+  (let ((page-num (read-number "Keep page number: " 1)))
+    (dwim-shell-command-on-marked-files
+     "Keep pdf page"
+     (format "qpdf '<<f>>' --pages . %d -- '<<fne>>_%d.<<e>>'" page-num page-num)
+     :utils "qpdf")))
+
+;;;###autoload
+(defun dwim-shell-commands-join-images-horizontally ()
+  "Join all marked images horizontally as a single image."
+  (interactive)
+  (let ((filename (format "joined.%s"
+                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
+    (dwim-shell-command-on-marked-files
+     "Join images horizontally"
+     (format "convert -verbose '<<*>>' +append '<<%s(u)>>'"
+             (dwim-shell-command-read-file-name
+              (format "Join as image named (default \"%s\"): " filename)
+              :default filename))
+     :utils "convert")))
+
+;;;###autoload
+(defun dwim-shell-commands-join-images-vertically ()
+  "Join all marked images vertically as a single image."
+  (interactive)
+  (let ((filename (format "joined.%s"
+                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
+    (dwim-shell-command-on-marked-files
+     "Join images vertically"
+     (format "convert -verbose '<<*>>' -gravity center -append '<<%s(u)>>'"
+             (dwim-shell-command-read-file-name
+              (format "Join as image named (default \"%s\"): " filename)
+              :default filename))
+     :utils "convert")))
+
+;;;###autoload
+(defun dwim-shell-commands-image-to-grayscale ()
+  "Convert all marked images to grayscale."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert image to grayscale"
+   "convert -verbose -type Grayscale '<<f>>' '<<fne>>_grayscale.<<e>>'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-reorient-image ()
+  "Reorient images."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Reorient image"
+   "convert -verbose -auto-orient '<<f>>' '<<fne>>_reoriented.<<e>>'"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-gif-to-video ()
+  "Convert all marked gif(s) to video(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to gif"
+   "ffmpeg -i '<<f>>' -movflags faststart -pix_fmt yuv420p -vf 'scale=trunc(iw/2)*2:trunc(ih/2)*2' '<<fne>>.mp4'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-empty-trash ()
+  "Empty macOS trash."
+  (interactive)
+  (when (y-or-n-p "Empty macOS trash? ")
+    (dwim-shell-command-on-marked-files
+     "Empty macOS trash"
+     "trash -e -y"
+     :silent-success t
+     :utils "trash")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-ocr-text-from-desktop-region ()
+  "Select a macOS desktop area to OCR and copy recognized text to kill ring."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "OCR area"
+   "macosrec --ocr"
+   ;; brew install xenodium/macosrec/macosrec
+   :utils "macosrec"
+   :on-completion
+   (lambda (buffer process)
+     (when-let ((success (= (process-exit-status process) 0))
+                (text (with-current-buffer buffer
+                        (string-trim (buffer-string)))))
+       (progn
+         (kill-new text)
+         (switch-to-buffer buffer)
+         (goto-char (point-min))
+         (message "OCR copied to clipboard"))))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-ocr-text-from-image ()
+  "OCR file and copy recognized text to kill ring."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "OCR area"
+   "macosrec --ocr --clipboard --input '<<f>>'"
+   ;; brew install xenodium/macosrec/macosrec
+   :utils "macosrec"
+   :on-completion
+   (lambda (buffer process)
+     (when-let ((success (= (process-exit-status process) 0))
+                (text (with-current-buffer buffer
+                        (string-trim (buffer-string)))))
+       (progn
+         (kill-new text)
+         (switch-to-buffer buffer)
+         (goto-char (point-min))
+         (message "OCR copied to clipboard"))))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-convert-to-mp4 ()
+  "Convert to mov to mp4"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to mov to mp4"
+   ;; "ffmpeg -loglevel quiet -stats -y -i <<f>>.mov -vcodec h264 -acodec copy <<fne>>.mp4"
+   ;; Found the encoder via ffmpeg -encoders | grep videotoolbox,
+   ;; source https://www.reddit.com/r/ffmpeg/comments/14pqeex/getting_0_gpu_utilization_with_apple_silicons/
+   "ffmpeg -i '<<f>>' -map_metadata 0 \
+     -c:v hevc_videotoolbox -q:v 35 -preset fast -c:a aac -b:a 128k -tag:v hvc1 '<<fne>>'.mp4"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-gif ()
+  "Convert all marked videos to gif(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to gif"
+   "ffmpeg -loglevel quiet -stats -y -i '<<f>>' -pix_fmt rgb24 -r 15 '<<fne>>.gif'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-mov ()
+  "Convert all marked videos to mov(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to mov"
+   "ffmpeg -i  '<<f>>' -c:v libx264 -c:a aac  '<<fne>>.mov'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-webp ()
+  "Convert all marked videos to webp(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to webp"
+   "ffmpeg -i '<<f>>' -vcodec libwebp -filter:v fps=fps=10 -compression_level 3 -lossless 1 -loop 0 -preset default -an -vsync 0 '<<fne>>'.webp"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-webp-to-video ()
+  "Convert all marked webp(s) to video(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert webp to video"
+   "convert '<<f>>' '<<td>>/<<bne>>.gif'
+    ffmpeg -i '<<td>>/<<bne>>.gif' -movflags faststart -pix_fmt yuv420p -vf 'scale=trunc(iw/2)*2:trunc(ih/2)*2' '<<fne>>.mp4'"
+   :utils '("ffmpeg" "convert")
+   :extensions "webp"))
+
+;;;###autoload
+(defun dwim-shell-commands-webp-to-gif ()
+  "Convert all marked webp(s) to gif(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert webp to video"
+   "convert '<<f>>' '<<fne>>.gif'"
+   :utils '("convert")
+   :extensions "webp"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-hevc-mkv ()
+  "Convert all marked videos to hevc mkv."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert video to h265 "
+   "REPO_DIR=/tmp/other_video_transcoding
+    if ! [ -d \"$REPO_DIR\" ]
+    then
+      git clone https://github.com/donmelton/other_video_transcoding.git $REPO_DIR
+    fi
+    pushd $REPO_DIR
+    git pull origin master || echo \"skipping repo update...\"
+    popd
+    ruby $REPO_DIR/bin/other-transcode --hevc '<<f>>'"
+   :utils '("git" "ffmpeg" "mkvtoolnix" "mpv")))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-optimized-gif ()
+  "Convert all marked videos to optimized gif(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to optimized gif"
+   "ffmpeg -loglevel quiet -stats -y -i '<<f>>' -pix_fmt rgb24 -r 15 '<<fne>>.gif'
+    gifsicle -O3 '<<fne>>.gif' --lossy=80 -o '<<fne>>.gif'"
+   :utils '("ffmpeg" "gifsicle")))
+
+;;;###autoload
+(defun dwim-shell-commands-unzip ()
+  "Unzip all marked archives (of any kind) using `atool'."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Unzip" "atool --extract --explain '<<f>>'"
+   :utils "atool"))
+
+;;;###autoload
+(defun dwim-shell-commands-zip ()
+  "Zip all marked files into archive.zip."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Zip" (if (eq 1 (seq-length (dwim-shell-command--files)))
+             "zip -r '<<fne>>.<<e>>' '<<f>>'"
+           "zip -r '<<archive.zip(u)>>' '<<*>>'")
+   :utils "zip"))
+
+;;;###autoload
+(defun dwim-shell-commands-zip-password-protect ()
+  "Protect/encrypt zip file(s) with password."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Add zip password" "zipcloak --output-file '<<fne>>_protected.<<e>>' '<<f>>'"
+   :extensions "zip"
+   :utils "zipcloak"))
+
+;;;###autoload
+(defun dwim-shell-commands-optimize-gif ()
+  "Convert all marked videos to optimized gif(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to optimized gif"
+   "gifsicle -O3 '<<f>>' --lossy=90 -o '<<fne>>_optimized.gif'"
+   :utils '("ffmpeg" "gifsicle")))
+
+;;;###autoload
+(defun dwim-shell-commands-speed-up-gif ()
+  "Speeds up gif(s)."
+  (interactive)
+  (let ((factor (string-to-number
+                 (completing-read "Speed up x times: " '("1" "1.5" "2" "2.5" "3" "4")))))
+    (dwim-shell-command-on-marked-files
+     "Speed up gif"
+     (format "gifsicle -U '<<f>>' <<frames>> -O2 -o '<<fne>>_x%s.<<e>>'" factor)
+     :extensions "gif" :utils '("gifsicle" "identify")
+     :post-process-template (lambda (script file)
+                              (string-replace "<<frames>>" (dwim-shell-commands--gifsicle-frames-every factor file) script)))))
+
+;;;###autoload
+(defun dwim-shell-commands-image-apply-ios-round-corners ()
+  "Apply iOS round corners to image(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+     "Speed up gif"
+     "set -o xtrace
+      width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 '<<f>>')
+      height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 '<<f>>')
+      corner=$((${width}/4))
+      echo ${corner}
+      convert -size ${width}x${height} xc:none -fill white -draw \"roundRectangle 0,0 ${width},${height} ${corner},${corner}\" '<<f>>' -compose SrcIn -composite '<<fne>>_ios_round.<<e>>'"
+     :utils '("ffprobe" "convert")))
+
+;;;###autoload
+(defun dwim-shell-commands-clip-round-rect-gif ()
+  "Clip gif(s) with round rectangle."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Clip round rect gif(s)"
+   "width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 '<<f>>')
+    height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 '<<f>>')
+    convert -quiet -size \"${width}x${height}\" xc:none -fill black -draw \"roundRectangle 0,0,${width},${height} <<Width width:27>>,<<Width width:27>>\" '<<td>>/mask.png'
+    convert  '<<f>>' -coalesce -background black -alpha remove -alpha off '<<td>>/no_alpha.<<e>>'
+    # https://stackoverflow.com/a/66990135
+    convert '<<td>>/no_alpha.<<e>>' -quiet -coalesce -alpha extract null: \\( '<<td>>/mask.png' -alpha extract \\) -compose multiply -layers composite '<<td>>/alpha.gif'
+    convert '<<td>>/no_alpha.<<e>>' null: '<<td>>/alpha.gif' -quiet -alpha off -compose copy_opacity -layers composite '<<fne>>_rounded.<<e>>'
+    # Turn looping on.
+    mogrify -loop 0 '<<fne>>_rounded.<<e>>'
+    gifsicle -O3  '<<fne>>_rounded.<<e>>' --lossy=80 -o '<<fne>>_rounded.<<e>>'"
+   :extensions "gif"
+   :utils '("ffprobe" "convert")))
+
+;;;###autoload
+(defun dwim-shell-commands-resize-gif ()
+  "Resize marked gif(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Resize marked gif(s)"
+   "gifsicle --scale <<Scaling factor:0.5>> '<<f>>' -o '<<fne>>_x<<Scaling factor:0.5>>.gif'"
+   :extensions "gif"
+   :utils "gifsicle"))
+
+;;;###autoload
+(defun dwim-shell-commands-epub-to-org ()
+  "Convert epub(s) to org."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "epub to org"
+   "pandoc --from=epub --to=org '<<f>>' > '<<fne>>.org'"
+   :extensions "epub"
+   :utils "pandoc"))
+
+;;;###autoload
+(defun dwim-shell-commands-docx-to-pdf ()
+  "Convert docx(s) to pdf (via latex)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "docx to pdf (via latex)"
+   "pandoc -t latex '<<f>>' -o '<<fne>>.pdf'"
+   :extensions "docx" ;; brew install mactex
+   :utils "pdflatex"))
+
+;;;###autoload
+(defun dwim-shell-commands-kill-process ()
+  "Select and kill process."
+  (interactive)
+  (let* ((pid-width 5)
+         (comm-width 25)
+         (user-width 10)
+         (processes (proced-process-attributes))
+         (candidates
+          (mapcar (lambda (attributes)
+                    (let* ((process (cdr attributes))
+                           (pid (format (format "%%%ds" pid-width) (map-elt process 'pid)))
+                           (user (format (format "%%-%ds" user-width)
+                                         (truncate-string-to-width
+                                          (map-elt process 'user) user-width nil nil t)))
+                           (comm (format (format "%%-%ds" comm-width)
+                                         (truncate-string-to-width
+                                          (map-elt process 'comm) comm-width nil nil t)))
+                           (args-width (- (window-width) (+ pid-width user-width comm-width 3)))
+                           (args (map-elt process 'args)))
+                      (cons (if args
+                                (format "%s %s %s %s" pid user comm (truncate-string-to-width args args-width nil nil t))
+                              (format "%s %s %s" pid user comm))
+                            process)))
+                  processes))
+         (selection (map-elt candidates
+                             (completing-read "kill process: "
+                                              (seq-sort
+                                               (lambda (p1 p2)
+                                                 (string-lessp (nth 2 (split-string (string-trim (car p1))))
+                                                               (nth 2 (split-string (string-trim (car p2))))))
+                                               candidates) nil t)))
+         (prompt-title (format "%s %s %s"
+                               (map-elt selection 'pid)
+                               (map-elt selection 'user)
+                               (map-elt selection 'comm))))
+    (when (y-or-n-p (format "Kill %s?" prompt-title))
+      (dwim-shell-command-on-marked-files
+       (format "Kill %s" prompt-title)
+       (format "kill -9 %d" (map-elt selection 'pid))
+       :utils "kill"
+       :error-autofocus t
+       :silent-success t))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-add-to-photos ()
+  "Add to Photos.app."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Add to Photos.app"
+   "osascript <<EOF
+  tell application \"Photos\"
+    import POSIX file \"<<f>>\"
+  end tell
+EOF"
+   :silent-success t
+   :utils "osascript"
+   :on-completion (lambda (buffer process)
+                    (if-let ((success (= (process-exit-status process) 0)))
+                        (progn
+                          (kill-buffer buffer)
+                          (start-process "Open Photos" nil "open" "-a" "Photos"))
+                      (switch-to-buffer buffer)))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-toggle-bluetooth-device-connection ()
+  "Toggle Bluetooth device connection."
+  (interactive)
+  (let* ((devices (seq-filter
+                   (lambda (line)
+                     ;; Keep lines like: af-8c-3b-b1-99-af - Device name
+                     (string-match-p "^[0-9a-f]\\{2\\}" line))
+                   (with-current-buffer (get-buffer-create "*BluetoothConnector*")
+                     (erase-buffer)
+                     ;; BluetoothConnector exits with 64 if no param is given.
+                     ;; Invoke with no params to get a list of devices.
+                     (unless (eq 64 (call-process "BluetoothConnector" nil (current-buffer)))
+                       (kill-buffer (current-buffer))
+                       (error (buffer-string)))
+                     (let ((lines (split-string (buffer-string) "\n")))
+                       (kill-buffer (current-buffer))
+                       lines))))
+         (candidates (mapcar (lambda (device)
+                               ;; key (device name) : value (address)
+                               (cons (nth 1 (split-string device " - "))
+                                     (nth 0 (split-string device " - "))))
+                             devices))
+         (selected-name (completing-read "Toggle connection: "
+                                         (seq-sort #'string-lessp candidates) nil t))
+         (address (map-elt candidates selected-name)))
+    (dwim-shell-command-on-marked-files
+     (format "Toggle %s" selected-name)
+     (format "BluetoothConnector %s --notify" address)
+     :utils "BluetoothConnector"
+     ;; :error-autofocus t
+     ;; :silent-success t
+     )))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-bin-plist-to-xml ()
+  "Convert binary plist to xml."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert binary plist to xml"
+   "plutil -convert xml1 -o '<<fne>>.xml' '<<f>>'"
+   :utils "plutil"))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-toggle-dark-mode ()
+  "Toggle macOS dark mode."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Toggle dark mode"
+   "dark-mode"
+   :utils "dark-mode" ;; brew install dark-mode
+   :silent-success t))
+
+;;;###autoload
+(defun dwim-shell-commands-pdf-to-txt ()
+  "Convert pdf to txt."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "pdf to txt"
+   "pdftotext -layout '<<f>>' '<<fne>>.txt'"
+   :utils "pdftotext"))
+
+;;;###autoload
+(defun dwim-shell-commands-resize-image-by-factor ()
+  "Resize marked image(s) by factor."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Resize image"
+   (let ((factor (read-number "Resize scaling factor: " 0.5)))
+     (format "convert -resize %%%d '<<f>>' '<<fne>>_x%.2f.<<e>>'"
+             (* 100 factor) factor))
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-resize-image-in-pixels ()
+  "Resize marked image(s) in pixels."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Resize image"
+   (let ((width (read-number "Resize width (pixels): " 500)))
+     (format "convert -resize %dx '<<f>>' '<<fne>>_x%d.<<e>>'" width width))
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-pdf-password-protect ()
+  "Add a password to pdf(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Password protect pdf"
+   (format "qpdf --verbose --encrypt '%s' '%s' 256 -- '<<f>>' '<<fne>>_protected.<<e>>'"
+           (read-passwd "user-password: ")
+           (read-passwd "owner-password: "))
+   :utils "qpdf"
+   :extensions "pdf"))
+
+;;;###autoload
+(defun dwim-shell-commands-pdf-password-unprotect ()
+  "Remove a password from pdf(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Remove protection from pdf"
+   (format "qpdf --verbose --decrypt --password='%s' -- '<<f>>' '<<fne>>_unprotected.<<e>>'"
+           (read-passwd "password: "))
+   :utils "qpdf"
+   :extensions "pdf"))
+
+(defun dwim-shell-commands--gifsicle-frames-every (skipping-every file)
+  "Generate frames SKIPPING-EVERY count for video FILE."
+  (string-join
+   (seq-map (lambda (n) (format "'#%d'" n))
+            (number-sequence 0 (string-to-number
+                                ;; Get total frames count.
+                                (seq-first (process-lines "identify" "-format" "%n\n" file)))
+                             skipping-every)) " "))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-mp3 ()
+  "Convert video(s) to mp3."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to mp3"
+   "ffmpeg -i '<<f>>' -vn -ab 128k -ar 44100 -y '<<fne>>.mp3'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-mp3-with-artwork ()
+  "Convert video(s) to mp3 (keep frame as artwork)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert to mp3"
+   "ffmpeg -i '<<f>>' -vf 'select=eq(n\\,0)' -q:v 3 cover.jpg -i '<<f>>' -vn -ab 128k -ar 44100 -y -map_metadata 0 -id3v2_version 3 -write_id3v1 1 -metadata:s:v title='Album cover' -metadata:s:v comment='Cover (front)' '<<fne>>.mp3'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-ndjson-to-org ()
+  "Convert ndjson file to org."
+  (interactive)
+  (unless (eq (length (dwim-shell-command--files)) 1)
+    (error "Only 1 file supported"))
+  (let* ((emacs-bin (file-truename (expand-file-name invocation-name
+                                                     invocation-directory)))
+         (source (nth 0 (dwim-shell-command--files)))
+         (destination (concat (file-name-sans-extension
+                               source) ".org"))
+         (fields (with-temp-buffer
+                   (insert-file-contents source)
+                   (buffer-substring-no-properties (point-min) (line-end-position))
+                   (read-string "Fields: " (mapconcat 'identity (mapcar (lambda (item)
+                                                                          (symbol-name (car item)))
+                                                                        (json-read-from-string
+                                                                         (buffer-substring-no-properties
+                                                                          (point-min) (line-end-position))))
+                                                      " ")))))
+    (dwim-shell-command-on-marked-files
+     "Convert ndjson to org"
+     (format "%s --quick --batch --eval \"%s\"" emacs-bin
+             (replace-regexp-in-string
+              "\"" "\\\\\""
+              (prin1-to-string
+               `(progn
+                  (require 'org)
+                  (require 'json)
+                  (defun convert-to-org-table (ndjson)
+                    (let ((rows (mapcar #'json-read-from-string
+                                        (split-string ndjson "\n" t))))
+                      (orgtbl-to-orgtbl
+                       (append
+                        (list (split-string ,fields))
+                        '(hline)
+                        (mapcar (lambda (obj)
+                                  (mapcar (lambda (key)
+                                            (or (alist-get (intern key) obj) ""))
+                                          (split-string ,fields)))
+                                rows)) nil)))
+                  (with-temp-buffer
+                    (insert-file-contents ,source)
+                    (let ((org (convert-to-org-table (buffer-string))))
+                      (with-temp-file ,destination
+                        (insert org))))))))
+     :extensions "ndjson")))
+
+;;;###autoload
+(defun dwim-shell-commands-set-media-artwork-image-metadata ()
+  "Set image artwork metadata for media file(s)."
+  (interactive)
+  (let ((artwork-file (file-name-unquote
+                       (read-file-name "Select artwork image: "
+                                       nil nil t)))
+        (should-backup (y-or-n-p "Create backup files? ")))
+    (unless (file-regular-p artwork-file)
+      (user-error "Not a file"))
+    (unless should-backup
+      (unless (y-or-n-p "Override file(s)? ")
+        (user-error "Aborted")))
+    (dwim-shell-command-on-marked-files
+     "Set album artwork"
+     (format (if should-backup
+                 "ffmpeg -i '<<f>>' -i '%s' -map_metadata 0 -map 0:a -map 1 -c copy -disposition:v:0 attached_pic '<<f>>.tmp.<<e>>' && mv -f '<<f>>' '<<f>>.bak' && mv '<<f>>.tmp.<<e>>' '<<f>>'"
+               "ffmpeg -i '<<f>>' -i '%s' -map_metadata 0 -map 0:a -map 1 -c copy -disposition:v:0 attached_pic '<<f>>.tmp.<<e>>' && mv -f '<<f>>.tmp.<<e>>' '<<f>>'")
+             artwork-file)
+     :utils "AtomicParsley"
+     :silent-success t)))
+
+;;;###autoload
+(defun dwim-shell-commands-video-trim-beginning ()
+  "Drop audio from all marked videos."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Trim beginning"
+   "ffmpeg -i '<<f>>' -y -ss <<Seconds:5>> -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'"
+   :silent-success t
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-trim-end ()
+  "Drop audio from all marked videos."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Trim beginning"
+   "ffmpeg -sseof -<<Seconds:5>> -i '<<f>>' -y -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'"
+   :silent-success t
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-video-to-thumbnail ()
+  "Generate a thumbnail for marked video(s)."
+  (interactive)
+  (let ((temp-dir (make-temp-file "thumbnails-" t)))
+    (dwim-shell-command-on-marked-files
+     "Thumbnail with ffmpeg"
+     "ffmpeg -i '<<f>>' -ss 00:00:01.000 -vframes 1 '<<fne>>.jpg'"
+     :utils "ffmpeg")))
+
+;;;###autoload
+(defun dwim-shell-commands-drop-video-audio ()
+  "Drop audio from all marked videos."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Drop audio"
+   "ffmpeg -i '<<f>>' -c copy -an '<<fne>>_no_audio.<<e>>'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-ping-google ()
+  "Ping google.com."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Ping google.com"
+   "ping -c 3 google.com"
+   :utils "ping"
+   :focus-now t))
+
+;;;###autoload
+(defun dwim-shell-commands-speed-up-video ()
+  "Speed up video(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Speed up video"
+   (let ((factor (read-number "Resize scaling factor: " 2)))
+     (format "ffmpeg -i '<<f>>' -an -filter:v 'setpts=%s*PTS' '<<fne>>_x%s.<<e>>'"
+             (/ 1 (float factor)) factor))
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-speed-up-video-fragment ()
+  "Speed up fragment in video(s)."
+  (interactive)
+  (let ((start (read-number "Start (seconds): "))
+        (end (read-number "End (seconds): "))
+        (factor (read-number "Speed up factor: " 2)))
+    (dwim-shell-command-on-marked-files
+     "Speed up fragment in video"
+     (format "ffmpeg -i '<<f>>' -filter_complex '[0:v]trim=start=0:end=%d,setpts=PTS-STARTPTS[v0];[0:v]trim=start=%d:end=%d,setpts=(PTS-1)/%d[v1];[0:v]trim=start=%d,setpts=PTS-STARTPTS[v2];[v0][v1][v2]concat=n=3:v=1:a=0' -preset fast '<<fne>>_%d:%dx%d.<<e>>'" start start end factor end start end factor)
+     :utils "ffmpeg")))
+
+;;;###autoload
+(defun dwim-shell-commands-resize-video ()
+  "Resize marked images."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Resize video"
+   "
+eval $(ffprobe -v quiet -show_format -of flat=s=_ -show_entries stream=width '<<f>>');
+width=${streams_stream_0_width};
+zmodload zsh/mathfunc
+width=$((rint($width * <<Scaling factor:0.5>>)));
+# Make it even or face 'not divisible by 2' errors.
+if [[ $((width%2)) -ne 0 ]] then
+  width=$(($width - 1))
+fi
+ffmpeg -n -i '<<f>>' -vf \"scale=$width:-2\" '<<fne>>_x<<Scaling factor:0.5>>.<<e>>'"
+   :utils "ffmpeg"))
+
+;;;###autoload
+(defun dwim-shell-commands-clipboard-to-qr ()
+  "Generate a QR code from clipboard."
+  (interactive)
+  (let ((temp-file (concat (temporary-file-directory) "qr-code")))
+    (dwim-shell-command-on-marked-files
+     "Generate a QR code from clipboard"
+     (format "qrencode -s10 -o %s %s" temp-file (shell-quote-argument (current-kill 0)))
+     :utils "qrencode"
+     :on-completion (lambda (buffer _process)
+                      (kill-buffer buffer)
+                      (switch-to-buffer (find-file-noselect temp-file t))))))
+
+;;;###autoload
+(defun dwim-shell-commands-sha-256-hash-file-at-clipboard-url ()
+  "Download file at clipboard URL and generate SHA-256 hash."
+  (interactive)
+  (unless (string-match-p "^http[s]?://" (current-kill 0))
+    (user-error "No URL in clipboard"))
+  (dwim-shell-command-on-marked-files
+   "Generate SHA-256 hash from clipboard URL."
+   "curl -s '<<cb>>' | sha256sum - | cut -d ' ' -f1"
+   :utils '("curl" "sha256sum")
+   :on-completion
+   (lambda (buffer process)
+     (if-let ((success (= (process-exit-status process) 0))
+              (hash (with-current-buffer buffer
+                      (string-trim (buffer-string)))))
+         (progn
+           (kill-buffer buffer)
+           (kill-new hash)
+           (message "Copied %s to clipboard"
+                    (propertize hash 'face 'font-lock-string-face)))
+       (switch-to-buffer buffer)))))
+
+;;;###autoload
+(defun dwim-shell-commands-view-sqlite-schema-diagram ()
+  "View sqlite schema diagram."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "View sqlite schema"
+   "set -e
+temp_dir=\"${TMPDIR:-/tmp/}\"
+file_name=\"sqlite-schema-diagram.sql\"
+file_path=\"${temp_dir}/${file_name}\"
+url=\"https://gitlab.com/Screwtapello/sqlite-schema-diagram/-/raw/main/sqlite-schema-diagram.sql\"
+
+if [[ ! -f \"$file_path\" ]]; then
+  curl -o \"$file_path\" \"$url\"
+fi
+
+sqlite3 -list \"<<f>>\" < $file_path > \"<<fne>>.dot\"
+dot -Tsvg \"<<fne>>.dot\" > \"<<fne>>.svg\"
+echo \"<<fne>>.svg\"
+"
+   :utils '("dot" "sqlite3")
+   :on-completion
+   (lambda (buffer process)
+     (if (= (process-exit-status process) 0)
+         (with-current-buffer buffer
+           (let ((svg-file (string-trim (buffer-string))))
+             (if (string-suffix-p "svg" svg-file)
+                 (progn
+                   (find-file svg-file)
+                   (kill-buffer buffer))
+               (switch-to-buffer buffer))))
+       (switch-to-buffer buffer)))))
+
+;;;###autoload
+(defun dwim-shell-commands-open-externally ()
+  "Open file(s) externally."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Open externally"
+   (if (eq system-type 'darwin)
+       (if (derived-mode-p 'prog-mode)
+           (format "xed --line %d '<<f>>'"
+                   (line-number-at-pos (point)))
+         "open '<<f>>'")
+     "xdg-open '<<f>>'")
+   :shell-util "zsh"
+   :shell-args '("-x" "-c")
+   :silent-success t
+   :utils (if (eq system-type 'darwin)
+              "open"
+            "xdg-open")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-caffeinate ()
+  "Invoke caffeinate to prevent mac from sleeping."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Caffeinate"
+   "caffeinate"
+   :utils "caffeinate"
+   :no-progress t
+   :focus-now t))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-make-finder-alias ()
+  "Make macOS Finder alias."
+  (interactive)
+  (let ((files (dwim-shell-command--files))
+        (target-dir (read-directory-name "Select target dir: " "/Applications" nil t)))
+    (dwim-shell-command-on-marked-files
+     "Make macOS alias"
+     (format "osascript -e 'tell application \"Finder\" to make alias file to POSIX file \"<<f>>\" at POSIX file \"%s\"'"
+             target-dir)
+     :utils "osascript"
+     :no-progress t
+     :silent-success t
+     :on-completion (lambda (buffer _process)
+                      (kill-buffer buffer)
+                      (dired-jump nil (file-name-concat target-dir (file-name-nondirectory (nth 0 files))))))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-version-and-hardware-overview-info ()
+  "View macOS version and hardware overview info."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "macOS hardware overview"
+   "sw_vers; system_profiler SPHardwareDataType"
+   :utils '("sw_vers" "system_profiler")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-reveal-in-finder ()
+  "Reveal selected files in macOS Finder."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Reveal in Finder"
+   "import AppKit
+    NSWorkspace.shared.activateFileViewerSelecting([\"<<*>>\"].map{URL(fileURLWithPath:$0)})"
+   :silent-success t
+   :shell-pipe "swift -"
+   :join-separator ", "
+   :utils "swift"))
+
+(defun dwim-shell-commands--macos-sharing-services ()
+  "Return a list of sharing services."
+  (let* ((source (format "import AppKit
+                         NSSharingService.sharingServices(forItems: [
+                           %s
+                         ]).forEach {
+                           print(\"\\($0.title)\")
+                         }"
+                         (string-join (mapcar (lambda (file)
+                                                (format "URL(fileURLWithPath: \"%s\")" file))
+                                              (dwim-shell-command--files))
+                                      ", ")))
+         (services (split-string
+                    (string-trim
+                     ;; Remove anything that doesn't start with alpha characters
+                     ;; There may be compilation warnings.
+                     (replace-regexp-in-string "^[^[:alpha:]].*\n" ""
+                                               (shell-command-to-string (format "echo '%s' | swift -" source))))
+                    "\n")))
+    (when (seq-empty-p services)
+      (error "No sharing services available"))
+    services))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-share ()
+  "Share selected files from macOS."
+  (interactive)
+  (let* ((services (dwim-shell-commands--macos-sharing-services))
+         (service-name (completing-read "Share via: " services))
+         (selection (seq-position services service-name #'string-equal)))
+    (dwim-shell-command-on-marked-files
+     "Share"
+     (format
+      "import AppKit
+
+       _ = NSApplication.shared
+
+       NSApp.setActivationPolicy(.regular)
+
+       class MyWindow: NSWindow, NSSharingServiceDelegate {
+         func sharingService(
+           _ sharingService: NSSharingService,
+           didShareItems items: [Any]
+         ) {
+           NSApplication.shared.terminate(nil)
+         }
+
+         func sharingService(
+           _ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error
+         ) {
+           let error = error as NSError
+           if error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError {
+             NSApplication.shared.terminate(nil)
+           }
+           exit(1)
+         }
+       }
+
+       let window = MyWindow(
+         contentRect: NSRect(x: 0, y: 0, width: 0, height: 0),
+         styleMask: [],
+         backing: .buffered,
+         defer: false)
+
+       let services = NSSharingService.sharingServices(forItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)})
+       let service = services[%s]
+       service.delegate = window
+       service.perform(withItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)})
+
+       NSApp.run()" selection)
+     :silent-success t
+     :shell-pipe "swift -"
+     :join-separator ", "
+     :no-progress t
+     :utils "swift")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-toggle-display-rotation ()
+  "Rotate display."
+  (interactive)
+  ;; #  Display_ID    Resolution  ____Display_Bounds____  Rotation
+  ;; 2  0x2b347692    1440x2560      0     0  1440  2560    270    [main]
+  ;; From fb-rotate output, get the `current-rotation' from Column 7, row 1 zero-based.
+  (let ((current-rotation (nth 7 (split-string (nth 1 (process-lines "fb-rotate" "-i"))))))
+    (dwim-shell-command-on-marked-files
+     "macOS hardware overview"
+     (format "fb-rotate -d 1 -r %s" (if (equal current-rotation "270") "0" "270"))
+     :utils "fb-rotate")))
+
+;;;###autoload
+(defun dwim-shell-commands-make-swift-package-library ()
+  "Create a swift package library"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Create a swift package library"
+   "swift package init --type library"
+   :utils "swift"))
+
+;;;###autoload
+(defun dwim-shell-commands-make-swift-package-executable ()
+  "Create a swift package executable"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Create a swift package executable"
+   "swift package init --type executable"
+   :utils "swift"))
+
+(defun dwim-shell-commands--macos-apps ()
+  "Return alist of macOS apps (\"Emacs\" . \"/Applications/Emacs.app\")."
+  (mapcar (lambda (path)
+            (cons (file-name-base path) path))
+          (seq-sort
+           #'string-lessp
+           (seq-mapcat (lambda (paths)
+                         (directory-files-recursively
+                          paths "\\.app$" t (lambda (path)
+                                              (not (string-suffix-p ".app" path)))))
+                       '("/Applications" "~/Applications" "/System/Applications")))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-set-default-app ()
+  "Set default app for file(s)."
+  (interactive)
+  (let* ((apps (dwim-shell-commands--macos-apps))
+         (selection (progn
+                      (cl-assert apps nil "No apps found")
+                      (completing-read "Set default app: " apps nil t))))
+    (dwim-shell-command-on-marked-files
+     "Set default app"
+     (format "duti -s \"%s\" '<<e>>' all"
+             (string-trim
+              (shell-command-to-string (format "defaults read '%s/Contents/Info.plist' CFBundleIdentifier"
+                                               (map-elt apps selection)))))
+     :silent-success t
+     :no-progress t
+     :utils "duti")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-open-with ()
+  "Open file(s) with specific external app."
+  (interactive)
+  (let* ((apps (dwim-shell-commands--macos-apps))
+         (selection (progn
+                      (cl-assert apps nil "No apps found")
+                      (completing-read "Open with: " apps nil t))))
+    (dwim-shell-command-on-marked-files
+     "Open with"
+     (format "open -a '%s' '<<*>>'" (map-elt apps selection))
+     :silent-success t
+     :no-progress t
+     :utils "open")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-open-with-firefox ()
+  "Open file(s) in Firefox."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Open in Firefox"
+   "open -a Firefox '<<*>>'"
+   :silent-success t
+   :no-progress t
+   :utils "open"))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-open-with-safari ()
+  "Open file(s) in Safari."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Open in Safari"
+   "open -a Safari '<<*>>'"
+   :silent-success t
+   :no-progress t
+   :utils "open"))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-start-recording-window ()
+  "Select and start recording a macOS window."
+  (interactive)
+  (let* ((window (dwim-shell-commands--macos-select-window))
+         (path (dwim-shell-commands--generate-path "~/Desktop" (car window) ".gif"))
+         (buffer-file-name path) ;; override so <<f>> picks it up
+         (inhibit-message t))
+    ;; Silence echo to avoid unrelated messages making into animation.
+    (cl-letf (((symbol-function 'dwim-shell-command--message)
+               (lambda (fmt &rest args) nil)))
+      (dwim-shell-command-on-marked-files
+       "Start recording a macOS window."
+       (format
+        "macosrec --record '%s' --gif --output '<<f>>'"
+        (cdr window))
+       :silent-success t
+       :monitor-directory "~/Desktop"
+       :no-progress t
+       :utils '("ffmpeg" "macosrec")
+       :on-completion
+       (lambda (buffer process)
+         (if (= (process-exit-status process) 0)
+             (progn
+               "Saved recording"
+               (dired-jump nil path)
+               (kill-buffer buffer))
+           (with-current-buffer buffer
+             (goto-char (point-min))
+             (if (search-forward "Aborted" nil t)
+                 (progn
+                   (message "Aborted recording")
+                   (kill-buffer buffer))
+               (switch-to-buffer buffer)))))))))
+
+(defun dwim-shell-commands--generate-path (dir name ext)
+  "Generate a timestamped path with DIR, NAME, and EXT."
+  (concat (file-name-as-directory (expand-file-name dir))
+          (format-time-string "%Y-%m-%d-%H:%M:%S-")
+          name ext))
+
+(defun dwim-shell-commands--macos-select-window ()
+  "Return a list of macOS windows."
+  (if-let* ((line (completing-read
+                   "Select: "
+                   (process-lines "macosrec" "--list") nil t))
+            (window-info (split-string line " "))
+            (window-number (string-to-number (nth 0 window-info)))
+            (window-app (nth 1 window-info))
+            (valid (> window-number 0)))
+      (cons window-app window-number)
+    (user-error "No window found")))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-end-recording-window ()
+  "Stop recording a macOS window."
+  (interactive)
+  (let ((inhibit-message t))
+    (cl-letf (((symbol-function 'dwim-shell-command--message)
+               (lambda (fmt &rest args) nil)))
+      (dwim-shell-command-on-marked-files
+       "End recording macOS window."
+       "macosrec --save"
+       :silent-success t
+       :no-progress t
+       :error-autofocus t
+       :utils "macosrec"))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-abort-recording-window ()
+  "Stop recording a macOS window."
+  (interactive)
+  (let ((inhibit-message t))
+    (cl-letf (((symbol-function 'dwim-shell-command--message)
+               (lambda (fmt &rest args) nil)))
+      (dwim-shell-command-on-marked-files
+       "Abort recording macOS window."
+       "macosrec --abort"
+       :silent-success t
+       :no-progress t
+       :utils "macosrec"))))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-screenshot-window ()
+  "Select and screenshot macOS window."
+  (interactive)
+  ;; Silence echo to avoid unrelated messages making into screenshot.
+  (let ((window (dwim-shell-commands--macos-select-window))
+        (inhibit-message t))
+    (dwim-shell-command-on-marked-files
+     "Start recording a macOS window."
+     (format "macosrec --screenshot %s" (cdr window))
+     :silent-success t
+     :monitor-directory "~/Desktop"
+     :no-progress t
+     :utils "macosrec")))
+
+;;;###autoload
+(defun dwim-shell-commands-files-combined-size ()
+  "Get files combined file size."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Get files combined file size"
+   "du -csh '<<*>>'"
+   :utils "du"
+   :on-completion (lambda (buffer _process)
+                    (with-current-buffer buffer
+                      (message "Total size: %s"
+                               (progn
+                                 (re-search-backward "\\(^[ 0-9.,]+[A-Za-z]+\\).*total$")
+                                 (match-string 1))))
+                    (kill-buffer buffer))))
+
+;;;###autoload
+(defun dwim-shell-commands-image-to-icns ()
+  "Convert png to icns icon."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Convert png to icns icon."
+   "
+    # Based on http://stackoverflow.com/questions/12306223/how-to-manually-create-icns-files-using-iconutil
+    # Note: png must be 1024x1024
+    mkdir '<<fne>>.iconset'
+    sips -z 16 16 '<<f>>' --out '<<fne>>.iconset/icon_16x16.png'
+    sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_16x16NOSPAM2x.png'
+    sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_32x32.png'
+    sips -z 64 64 '<<f>>' --out '<<fne>>.iconset/icon_32x32NOSPAM2x.png'
+    sips -z 128 128 '<<f>>' --out '<<fne>>.iconset/icon_128x128.png'
+    sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_128x128NOSPAM2x.png'
+    sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_256x256NOSPAM2x.png'
+    sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_512x512.png'
+    sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_256x256NOSPAM2x.png'
+    sips -z 1024 1024 '<<f>>' --out '<<fne>>.iconset/icon_512x512NOSPAM2x.png'
+    iconutil -c icns '<<fne>>.iconset'"
+   :utils '("sips" "iconutil")
+   :extensions "png"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-add-drop-shadow ()
+  "Add a drop shadow."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Add a drop shadow."
+   "convert <<f>> -bordercolor white -border 13 \\( +clone -background black -shadow 80x3+2+2 \\) +swap -background white -layers merge +repage <<fne>>-shadow.<<e>>"
+   :utils "convert"))
+
+;;;###autoload
+(defun dwim-shell-commands-image-trim-borders ()
+  "Trim image(s) border (useful for video screenshots)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Trim image border"
+   "magick convert -fuzz 3% -define trim:percent-background=0% -trim +repage '<<f>>' '<<fne>>_trimmed.<<e>>'"
+   :utils "magick"))
+
+;;;###autoload
+(defun dwim-shell-commands-git-clone-clipboard-url-to-downloads ()
+  "Clone git URL in clipboard to \"~/Downloads/\"."
+  (interactive)
+  (cl-assert (or (string-match-p "^\\(http\\|https\\|ssh\\)://" (current-kill 0))
+                 (string-match-p "^git@" (current-kill 0))) nil "No URL in clipboard")
+  (let* ((url (current-kill 0))
+         (download-dir (expand-file-name "~/Downloads/"))
+         (project-dir (concat download-dir (file-name-base url)))
+         (default-directory download-dir))
+    (when (or (not (file-exists-p project-dir))
+              (when (y-or-n-p (format "%s exists.  delete?" (file-name-base url)))
+                (delete-directory project-dir t)
+                t))
+      (dwim-shell-command-on-marked-files
+       (format "Clone %s" (file-name-base url))
+       (format "git clone %s" url)
+       :utils "git"
+       :on-completion (lambda (buffer _process)
+                        (kill-buffer buffer)
+                        (dired project-dir))))))
+
+;;;###autoload
+(defun dwim-shell-commands-http-serve-dir ()
+  "HTTP serve current directory."
+  (interactive)
+  (cond ((executable-find "python3")
+         (dwim-shell-command-on-marked-files
+          "HTTP serve current dir"
+          "python3 -m http.server"
+          :utils "python3"
+          :focus-now t
+          :no-progress t))
+        ((executable-find "python2")
+         (dwim-shell-command-on-marked-files
+          "HTTP serve current dir"
+          "python2 -m SimpleHTTPServer"
+          :utils "python2"
+          :focus-now t
+          :no-progress t))
+        ((executable-find "python")
+         (dwim-shell-command-on-marked-files
+          "HTTP serve current dir"
+          "python -m SimpleHTTPServer"
+          :utils "python"
+          :focus-now t
+          :no-progress t))
+        (t
+         (error "No python found"))))
+
+;;;###autoload
+(defun dwim-shell-commands-git-clone-clipboard-url ()
+  "Clone git URL in clipboard to `default-directory'."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   (format "Clone %s" (file-name-base (current-kill 0)))
+   "git clone <<cb>>"
+   :utils "git"))
+
+;;;###autoload
+(defun dwim-shell-commands-pass-git-pull ()
+  "Pass git pull."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "pass git pull"
+   "pass git pull"
+   :utils '("pass" "git")
+   :silent-success t))
+
+;;;###autoload
+(defun dwim-shell-commands-git-list-untracked-files ()
+  "List untracked git files in `default-directory'."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "List untracked"
+   "git ls-files --others ."
+   :utils "git"
+   :focus-now t))
+
+;;;###autoload
+(defun dwim-shell-commands-git-delete-untracked-files ()
+  "Delete untracked git files in `default-directory'."
+  (interactive)
+  (when (y-or-n-p (format "Clean '%s'? \n\n%s\n...\n\n"
+                          default-directory
+                          (string-join
+                           (seq-take (process-lines "git" "ls-files" "--others" ".") 3)
+                           "\n")))
+    (dwim-shell-command-on-marked-files
+     "Clean untracked"
+     "git clean -f ."
+     :utils "git"
+     :silent-success t)))
+
+;;;###autoload
+(defun dwim-shell-commands-external-ip ()
+  "Copy external IP to kill ring."
+  (interactive)
+  (let ((ip (car (last (process-lines "curl" "ifconfig.me")))))
+    (kill-new ip)
+    (message "Copied %s" ip)))
+
+;;;###autoload
+(defun dwim-shell-commands-macos-install-iphone-device-ipa ()
+  "Install iPhone device .ipa.
+Needs ideviceinstaller and libmobiledevice installed."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Install .ipa"
+   "ideviceinstaller -i '<<f>>'"
+   :utils "ideviceinstaller"))
+
+;;;###autoload
+(defun dwim-shell-commands-copy-to-downloads ()
+  "Copy file to ~/Downloads."
+  (interactive)
+  (dwim-shell-command-foreach
+   (lambda (file)
+     (copy-file file "~/Downloads/" 1)
+     (file-name-concat "~/Downloads" (file-name-nondirectory file)))
+   :monitor-directory "~/Downloads"))
+
+;;;###autoload
+(defun dwim-shell-commands-duplicate ()
+  "Duplicate file."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Duplicate file(s)."
+   "cp -R '<<f>>' '<<f(u)>>'"
+   :utils "cp"))
+
+;;;###autoload
+(defun dwim-shell-commands-rename-all ()
+  "Rename all marked file(s)."
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Rename all"
+   "mv '<<f>>' '<<New name:Renamed>>(<<n>>).<<e>>'"
+   :utils "mv"))
+
+;;;###autoload
+(defun dwim-shell-commands-move-to-downloads ()
+  "Move file to ~/Downloads."
+  (interactive)
+  (dwim-shell-command-foreach
+   (lambda (file)
+     (rename-file file "~/Downloads/" 1)
+     (when (buffer-file-name)
+       (rename-buffer (file-name-nondirectory file))
+       (set-visited-file-name
+        (file-name-concat "~/Downloads" (file-name-nondirectory file)))
+       (set-buffer-modified-p nil))
+     (file-name-concat "~/Downloads" (file-name-nondirectory file)))
+   :monitor-directory "~/Downloads"))
+
+;;;###autoload
+(defun dwim-shell-commands-copy-to-desktop ()
+  "Copy file to ~/Desktop."
+  (interactive)
+  (dwim-shell-command-foreach
+   (lambda (file)
+     (copy-file file "~/Desktop/" 1)
+     (file-name-concat "~/Desktop" (file-name-nondirectory file)))
+   :monitor-directory "~/Desktop"))
+
+;;;###autoload
+(defun dwim-shell-commands-move-to-desktop ()
+  "Move file to ~/Desktop."
+  (interactive)
+  (dwim-shell-command-foreach
+   (lambda (file)
+     (rename-file file "~/Desktop/" 1)
+     (when (buffer-file-name)
+       (rename-buffer (file-name-nondirectory file))
+       (set-visited-file-name
+        (file-name-concat "~/Desktop" (file-name-nondirectory file)))
+       (set-buffer-modified-p nil))
+     (file-name-concat "~/Desktop" (file-name-nondirectory file)))
+   :monitor-directory "~/Desktop"))
+
+;;;###autoload
+(defun dwim-shell-commands-kill-gpg-agent ()
+  "Kill (thus restart) gpg agent.
+
+Useful for when you get this error:
+
+gpg: public key decryption failed: No pinentry
+gpg: decryption failed: No pinentry"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "Kill gpg agent"
+   "gpgconf --kill gpg-agent"
+   :utils "gpgconf"
+   :silent-success t))
+
+;; Based on
+;; https://apps.bram85.nl/git/bram/gists/src/commit/31ac3363da925daafa2420b7f96c67612ca28241/gists/dwim-0x0-upload.el
+;;;###autoload
+(defun dwim-shell-commands-upload-to-0x0 ()
+  "Upload the marked files to 0x0.st"
+  (interactive)
+  (dwim-shell-command-on-marked-files
+   "0x0 upload"
+   "curl -Ffile=@<<f>> -Fsecret= https://0x0.st"
+   :utils "curl"
+   :post-process-template
+   ;; Insert the single quotes at the appropriate place according to
+   ;; 0x0.st example online:
+   ;; curl -F'file=@yourfile.png' -Fsecret= https://0x0.st
+   ;;
+   ;; The placement of these single quotes confuse the escaping
+   ;; mechanisms of dwim-shell-command, as it considers @ as the
+   ;; opening 'quote' as it appears right in front of <<f>>.
+   (lambda (template path)
+     (string-replace "-Ffile" "-F'file"
+                     (string-replace path (concat path "'") template)))
+   :on-completion
+   (lambda (buffer process)
+     (if (= (process-exit-status process) 0)
+         (with-current-buffer buffer
+           (let ((url (car (last (split-string (string-trim (buffer-string)) "\n")))))
+             (eww url)
+             (kill-new url)
+             (message "Copied: %s" (current-kill 0)))
+           (kill-buffer buffer))
+       (switch-to-buffer buffer)))))
+
+(provide 'dwim-shell-commands)
+
+;;; dwim-shell-commands.el ends here