diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/auth-config.el | 82 | ||||
| -rw-r--r-- | modules/config-utilities.el | 12 | ||||
| -rw-r--r-- | modules/dirvish-config.el | 17 | ||||
| -rw-r--r-- | modules/org-gcal-config.el | 5 | ||||
| -rw-r--r-- | modules/system-utils.el | 13 | ||||
| -rw-r--r-- | modules/transcription-config.el | 96 | ||||
| -rw-r--r-- | modules/video-audio-recording.el | 12 |
7 files changed, 188 insertions, 49 deletions
diff --git a/modules/auth-config.el b/modules/auth-config.el index 6b8a8ddb..2b52087e 100644 --- a/modules/auth-config.el +++ b/modules/auth-config.el @@ -24,9 +24,11 @@ :ensure nil ;; built in :demand t ;; load this package immediately :config - (setenv "GPG_AGENT_INFO" nil) ;; disassociate with external gpg agent - (setq auth-sources `(,authinfo-file)) ;; use authinfo.gpg (see user-constants.el) - (setq auth-source-debug t)) ;; echo debug info to Messages + ;; USE gpg-agent for passphrase caching (400-day cache from gpg-agent.conf) + ;; (setenv "GPG_AGENT_INFO" nil) ;; DISABLED: was preventing gpg-agent cache + (setq auth-sources `(,authinfo-file)) ;; use authinfo.gpg (see user-constants.el) + (setq auth-source-debug t) ;; echo debug info to Messages + (setq auth-source-cache-expiry 86400)) ;; cache decrypted credentials for 24 hours ;; ----------------------------- Easy PG Assistant ----------------------------- ;; Key management, cryptographic operations on regions and files, dired @@ -40,5 +42,79 @@ ;; (setq epa-pinentry-mode 'loopback) ;; emacs request passwords in minibuffer (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1) +;; ---------------------------------- Plstore ---------------------------------- +;; Encrypted storage used by oauth2-auto for Google Calendar tokens. +;; CRITICAL: Enable passphrase caching to prevent password prompts every 10 min. + +(use-package plstore + :ensure nil ;; built-in + :demand t + :config + ;; Cache passphrase indefinitely (relies on gpg-agent for actual caching) + (setq plstore-cache-passphrase-for-symmetric-encryption t) + ;; Allow gpg-agent to cache the passphrase (400 days per gpg-agent.conf) + (setq plstore-encrypt-to nil)) ;; Use symmetric encryption, not key-based + +;; ------------------------ Authentication Reset Utility ----------------------- + +(defun cj/reset-auth-cache (&optional include-gpg-agent) + "Reset authentication caches when wrong password was entered. + +By default, only clears Emacs-side caches (auth-source, EPA file +handler) and leaves gpg-agent's long-term cache intact. This preserves +your 400-day cache for GPG and SSH passphrases. + +With prefix argument INCLUDE-GPG-AGENT (\\[universal-argument]), also +clears gpg-agent's password cache. Use this when gpg-agent itself has +cached an incorrect password. + +Clears: +1. auth-source cache (Emacs-level credential cache) +2. EPA file handler cache (encrypted file cache) +3. gpg-agent cache (only if INCLUDE-GPG-AGENT is non-nil) + +Use this when you see errors like: + - \"Bad session key\" + - \"Decryption failed\" + - GPG repeatedly using wrong cached password" + (interactive "P") + (message "Resetting authentication caches...") + + ;; Clear auth-source cache (Emacs credential cache) + (auth-source-forget-all-cached) + + ;; Clear EPA file handler cache + (when (fboundp 'epa-file-clear-cache) + (epa-file-clear-cache)) + + ;; Only clear gpg-agent cache if explicitly requested + (if include-gpg-agent + (let ((result (shell-command "echo RELOADAGENT | gpg-connect-agent"))) + (if (zerop result) + (message "✓ Emacs and gpg-agent caches cleared. Next access will prompt for password.") + (message "⚠ Warning: Failed to clear gpg-agent cache"))) + (message "✓ Emacs caches cleared. GPG/SSH passphrases preserved for session."))) + +(defun cj/kill-gpg-agent () + "Force kill gpg-agent (it will restart automatically on next use). + +This is a more aggressive reset than `cj/reset-auth-cache'. Use this +when gpg-agent is stuck or behaving incorrectly. + +The gpg-agent will automatically restart on the next GPG operation." + (interactive) + (let ((result (shell-command "gpgconf --kill gpg-agent"))) + (if (zerop result) + (message "✓ gpg-agent killed. It will restart automatically on next use.") + (message "⚠ Warning: Failed to kill gpg-agent")))) + +;; Keybindings +(with-eval-after-load 'keybindings + (keymap-set cj/custom-keymap "A" #'cj/reset-auth-cache)) + +(with-eval-after-load 'which-key + (which-key-add-key-based-replacements + "C-; A" "reset auth cache")) + (provide 'auth-config) ;;; auth-config.el ends here. diff --git a/modules/config-utilities.el b/modules/config-utilities.el index 32018371..2af3effa 100644 --- a/modules/config-utilities.el +++ b/modules/config-utilities.el @@ -33,8 +33,7 @@ "C-c d i b" "info build" "C-c d i p" "info packages" "C-c d i f" "info features" - "C-c d r" "reload init" - "C-c d a" "reset auth cache")) + "C-c d r" "reload init")) ;;; --------------------------------- Profiling --------------------------------- @@ -283,15 +282,6 @@ Recompile natively when supported, otherwise fall back to byte compilation." (load-file user-init-file)) (keymap-set cj/debug-config-keymap "r" 'cj/reload-init-file) -;; ----------------------------- Reset-Auth-Sources ---------------------------- - -(defun cj/reset-auth-cache () - "Clear Emacs auth-source cache." - (interactive) - (auth-source-forget-all-cached) - (message "Emacs auth-source cache cleared.")) -(keymap-set cj/debug-config-keymap "a" 'cj/reset-auth-cache) - ;; ------------------------ Validate Org Agenda Entries ------------------------ (defun cj/validate-org-agenda-timestamps () diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 441ff61b..b10c97f0 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -8,15 +8,15 @@ ;; ediff, playlist creation, path copying, and external file manager integration. ;; ;; Key Bindings: -;; - d: Duplicate file at point (adds "-copy" before extension) -;; - D: Delete marked files immediately (dired-do-delete) +;; - d: Delete marked files (dired-do-delete) +;; - D: Duplicate file at point (adds "-copy" before extension) ;; - g: Quick access menu (jump to predefined directories) ;; - G: Search with deadgrep in current directory ;; - f: Open system file manager in current directory ;; - o/O: Open file with xdg-open/custom command ;; - l: Copy file path (project-relative or home-relative) ;; - L: Copy absolute file path -;; - P: Create M3U playlist from marked audio files +;; - P: Copy file path (same as 'l', replaces dired-do-print) ;; - M-D: DWIM menu (context actions for files) ;; - TAB: Toggle subtree expansion ;; - F11: Toggle sidebar view @@ -120,9 +120,9 @@ Filters for audio files, prompts for the playlist name, and saves the resulting (setq dired-listing-switches "-l --almost-all --human-readable --group-directories-first") (setq dired-dwim-target t) (setq dired-clean-up-buffers-too t) ;; offer to kill buffers associated deleted files and dirs - (setq dired-clean-confirm-killing-deleted-buffers t) ;; don't ask; just kill buffers associated with deleted files - (setq dired-recursive-copies (quote always)) ;; “always” means no asking - (setq dired-recursive-deletes (quote top))) ;; “top” means ask once + (setq dired-clean-confirm-killing-deleted-buffers nil) ;; don't ask; just kill buffers associated with deleted files + (setq dired-recursive-copies (quote always)) ;; "always" means no asking + (setq dired-recursive-deletes (quote top))) ;; "top" means ask once ;; note: disabled as it prevents marking and moving files to another directory ;; (setq dired-kill-when-opening-new-dired-buffer t) ;; don't litter by leaving buffers when navigating directories @@ -322,13 +322,14 @@ regardless of what file or subdirectory the point is on." ("M-p" . dirvish-peek-toggle) ("M-s" . dirvish-setup-menu) ("TAB" . dirvish-subtree-toggle) - ("d" . cj/dirvish-duplicate-file) + ("d" . dired-do-delete) + ("D" . cj/dirvish-duplicate-file) ("f" . cj/dirvish-open-file-manager-here) ("g" . dirvish-quick-access) ("o" . cj/xdg-open) ("O" . cj/open-file-with-command) ; Prompts for command to run ("r" . dirvish-rsync) - ("P" . cj/dired-create-playlist-from-marked) + ("P" . cj/dired-copy-path-as-kill) ("s" . dirvish-quicksort) ("v" . dirvish-vc-menu) ("y" . dirvish-yank-menu))) diff --git a/modules/org-gcal-config.el b/modules/org-gcal-config.el index 28cc1933..97e8446a 100644 --- a/modules/org-gcal-config.el +++ b/modules/org-gcal-config.el @@ -165,9 +165,8 @@ Useful after changing `cj/org-gcal-sync-interval-minutes'." (setq org-gcal-managed-update-existing-mode "gcal") ;; GCal wins on conflicts :config - ;; Enable plstore passphrase caching after org-gcal loads - (require 'plstore) - (setq plstore-cache-passphrase-for-symmetric-encryption t) + ;; Plstore caching is now configured globally in auth-config.el + ;; to ensure it loads before org-gcal needs it ;; set org-gcal timezone based on system timezone (setq org-gcal-local-timezone (cj/detect-system-timezone)) diff --git a/modules/system-utils.el b/modules/system-utils.el index 2b39d862..e9686777 100644 --- a/modules/system-utils.el +++ b/modules/system-utils.el @@ -186,13 +186,22 @@ Logs output and exit code to buffer *external-open.log*." ;;; -------------------------- Scratch Buffer Happiness ------------------------- (defvar scratch-emacs-version-and-system - (concat ";; Emacs " emacs-version + (concat "# Emacs " emacs-version " on " system-configuration ".\n")) (defvar scratch-greet - (concat ";; Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n")) + (concat "# Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n")) (setopt initial-scratch-message (concat scratch-emacs-version-and-system scratch-greet)) +;; Set scratch buffer to org-mode +(setopt initial-major-mode 'org-mode) + +;; Move cursor to end of scratch buffer on startup +(add-hook 'emacs-startup-hook + (lambda () + (when (string= (buffer-name) "*scratch*") + (goto-char (point-max))))) + ;;; --------------------------------- Dictionary -------------------------------- (use-package quick-sdcv diff --git a/modules/transcription-config.el b/modules/transcription-config.el index 13b9bbce..fd2f4aaa 100644 --- a/modules/transcription-config.el +++ b/modules/transcription-config.el @@ -5,19 +5,23 @@ ;;; Commentary: ;; -;; Audio transcription workflow using OpenAI Whisper (API or local). +;; Audio transcription workflow with multiple backend options. ;; ;; USAGE: ;; In dired: Press `T` on an audio file to transcribe ;; Anywhere: M-x cj/transcribe-audio ;; View active: M-x cj/transcriptions-buffer +;; Switch backend: C-; T b (or M-x cj/transcription-switch-backend) ;; ;; OUTPUT FILES: ;; audio.m4a → audio.txt (transcript) ;; → audio.log (process logs, conditionally kept) ;; ;; BACKENDS: -;; - 'openai-api: Fast cloud transcription (requires OPENAI_API_KEY) +;; - 'openai-api: Fast cloud transcription +;; API key retrieved from authinfo.gpg (machine api.openai.com) +;; - 'assemblyai: Cloud transcription with speaker diarization +;; API key retrieved from authinfo.gpg (machine api.assemblyai.com) ;; - 'local-whisper: Local transcription (requires whisper installed) ;; ;; NOTIFICATIONS: @@ -33,12 +37,14 @@ (require 'dired) (require 'notifications) +(require 'auth-source) ;; ----------------------------- Configuration --------------------------------- -(defvar cj/transcribe-backend 'local-whisper +(defvar cj/transcribe-backend 'assemblyai "Transcription backend to use. - `openai-api': Fast cloud transcription via OpenAI API +- `assemblyai': Cloud transcription with speaker diarization via AssemblyAI - `local-whisper': Local transcription using installed Whisper") (defvar cj/transcription-keep-log-when-done nil @@ -83,9 +89,36 @@ SUCCESS-P indicates whether transcription succeeded." "Return absolute path to transcription script based on backend." (let ((script-name (pcase cj/transcribe-backend ('openai-api "oai-transcribe") + ('assemblyai "assemblyai-transcribe") ('local-whisper "local-whisper")))) (expand-file-name (concat "scripts/" script-name) user-emacs-directory))) +(defun cj/--get-openai-api-key () + "Retrieve OpenAI API key from authinfo.gpg. +Expects entry in authinfo.gpg: + machine api.openai.com login api password sk-... +Returns the API key string, or nil if not found." + (when-let* ((auth-info (car (auth-source-search + :host "api.openai.com" + :require '(:secret)))) + (secret (plist-get auth-info :secret))) + (if (functionp secret) + (funcall secret) + secret))) + +(defun cj/--get-assemblyai-api-key () + "Retrieve AssemblyAI API key from authinfo.gpg. +Expects entry in authinfo.gpg: + machine api.assemblyai.com login api password <key> +Returns the API key string, or nil if not found." + (when-let* ((auth-info (car (auth-source-search + :host "api.assemblyai.com" + :require '(:secret)))) + (secret (plist-get auth-info :secret))) + (if (functionp secret) + (funcall secret) + secret))) + ;; ---------------------------- Process Management ----------------------------- (defun cj/--notify (title message &optional urgency) @@ -125,14 +158,28 @@ Returns the process object." (format "Audio file: %s\n" audio-file) (format "Script: %s\n\n" script))) - ;; Start process - (let ((process (make-process - :name process-name - :buffer (get-buffer-create buffer-name) - :command (list script audio-file) - :sentinel (lambda (proc event) - (cj/--transcription-sentinel proc event audio-file txt-file log-file)) - :stderr log-file))) + ;; Start process with environment + (let* ((process-environment + ;; Add API key to environment based on backend + (pcase cj/transcribe-backend + ('openai-api + (if-let ((api-key (cj/--get-openai-api-key))) + (cons (format "OPENAI_API_KEY=%s" api-key) + process-environment) + (user-error "OpenAI API key not found in authinfo.gpg for host api.openai.com"))) + ('assemblyai + (if-let ((api-key (cj/--get-assemblyai-api-key))) + (cons (format "ASSEMBLYAI_API_KEY=%s" api-key) + process-environment) + (user-error "AssemblyAI API key not found in authinfo.gpg for host api.assemblyai.com"))) + (_ process-environment))) + (process (make-process + :name process-name + :buffer (get-buffer-create buffer-name) + :command (list script audio-file) + :sentinel (lambda (proc event) + (cj/--transcription-sentinel proc event audio-file txt-file log-file)) + :stderr log-file))) ;; Track transcription (push (list process audio-file (current-time) 'running) cj/transcriptions-list) @@ -298,6 +345,21 @@ Uses backend specified by `cj/transcribe-backend'." (kill-process process) (message "Killed transcription process"))) +;;;###autoload +(defun cj/transcription-switch-backend () + "Switch transcription backend. +Prompts with completing-read to select from available backends." + (interactive) + (let* ((backends '(("assemblyai" . assemblyai) + ("openai-api" . openai-api) + ("local-whisper" . local-whisper))) + (current (symbol-name cj/transcribe-backend)) + (prompt (format "Transcription backend (current: %s): " current)) + (choice (completing-read prompt backends nil t)) + (new-backend (alist-get choice backends nil nil #'string=))) + (setq cj/transcribe-backend new-backend) + (message "Transcription backend: %s" choice))) + ;; ------------------------------- Dired Integration --------------------------- (with-eval-after-load 'dired @@ -311,16 +373,18 @@ Uses backend specified by `cj/transcribe-backend'." (defvar-keymap cj/transcribe-map :doc "Keymap for transcription operations" "a" #'cj/transcribe-audio + "b" #'cj/transcription-switch-backend "v" #'cj/transcriptions-buffer "k" #'cj/transcription-kill) -(keymap-set cj/custom-keymap "t" cj/transcribe-map) +(keymap-set cj/custom-keymap "T" cj/transcribe-map) (with-eval-after-load 'which-key (which-key-add-key-based-replacements - "C-; t" "transcription menu" - "C-; t a" "transcribe audio" - "C-; t v" "view transcriptions" - "C-; t k" "kill transcription")) + "C-; T" "transcription menu" + "C-; T a" "transcribe audio" + "C-; T b" "switch backend" + "C-; T v" "view transcriptions" + "C-; T k" "kill transcription")) (provide 'transcription-config) ;;; transcription-config.el ends here diff --git a/modules/video-audio-recording.el b/modules/video-audio-recording.el index c714a0a6..45bab267 100644 --- a/modules/video-audio-recording.el +++ b/modules/video-audio-recording.el @@ -4,7 +4,7 @@ ;;; Commentary: ;; Use ffmpeg to record desktop video or just audio. ;; with audio from mic and audio from default audio sink -;; Also supports audio-only recording in Opus format. +;; Audio recordings use M4A/AAC format for best compatibility. ;; ;; Note: video-recordings-dir and audio-recordings-dir are defined ;; (and directory created) in user-constants.el @@ -311,16 +311,16 @@ Otherwise use the default location in `audio-recordings-dir'." (system-device (cdr devices)) (location (expand-file-name directory)) (name (format-time-string "%Y-%m-%d-%H-%M-%S")) - (filename (expand-file-name (concat name ".opus") location)) + (filename (expand-file-name (concat name ".m4a") location)) (ffmpeg-command (format (concat "ffmpeg " "-f pulse -i %s " "-ac 1 " "-f pulse -i %s " - "-ac 2 " - "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2\" " - "-c:a libopus " - "-b:a 96k " + "-ac 1 " + "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2[out];[out]pan=mono|c0=0.5*c0+0.5*c1\" " + "-c:a aac " + "-b:a 64k " "%s") mic-device system-device |
