#+title: Aner's Emacs Lauretta Configuration #+property: header-args :results silent * Base setup ** Require #+begin_src emacs-lisp (require 'azos-emacs-base) (require 'azos-emacs-dev) (require 'azos-emacs-editor) (require 'azos-emacs-exwm) (require 'azos-emacs-station) #+end_src * Lauretta specific ** Weather #+begin_src emacs-lisp (setq wttrin-default-cities '("Meitar, Israel")) #+end_src ** LLM #+begin_src emacs-lisp (setq gptel-model 'openai/gpt-oss-120b gptel-backend (gptel-make-openai "Groq" :host "api.groq.com" :endpoint "/openai/v1/chat/completions" :stream t :key "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" :models '(openai/gpt-oss-120b))) #+end_src ** Agent Shell #+begin_src emacs-lisp (setq agent-shell-preferred-agent-config 'claude-code) #+end_src ** Headphones #+begin_src emacs-lisp (defun azos/connect-headphones (&optional arg) "Connect to headphones via bluetoothctl. Without prefix ARG, connects to the first device in the list. With prefix ARG, prompts to choose from all configured devices." (interactive "P") (if (not arg) (start-process-shell-command "connect-headphones" nil "azos-connect-headphones") (let* ((raw (shell-command-to-string "azos-connect-headphones --list")) (lines (split-string (string-trim raw) "\n" t)) (choices (mapcar (lambda (line) (let ((space (string-match " " line))) (cons (substring line (1+ space)) (substring line 0 space)))) lines)) (name (completing-read "Connect headphones: " (mapcar #'car choices) nil t)) (mac (cdr (assoc name choices)))) (start-process-shell-command "connect-headphones" nil (concat "azos-connect-headphones --mac " mac))))) (define-key azos/global-minor-mode/open-keymap (kbd "h") 'azos/connect-headphones) #+end_src ** Tab bar setup #+begin_src emacs-lisp (when (fboundp #'azos/bat/enable-tab-display) (azos/bat/enable-tab-display)) (when (fboundp #'azos/network/enable-tab-display) (azos/network/enable-tab-display)) (when (fboundp #'azos/audio/enable-tab-display) (azos/audio/enable-tab-display)) #+end_src ** Nixpkgs Search #+begin_src emacs-lisp (defun azos/nixpkgs-parse-json-lines (str) "Parse JSON Lines format (one JSON object per line) from STR." (mapcar (lambda (line) (and (not (string-empty-p line)) (string-match "^{" line) (condition-case nil (json-parse-string line) (error nil)))) (split-string (string-trim str) "\n" t))) (defun azos/nixpkgs-g (key hash) "Get KEY from HASH table." (when (hash-table-p hash) (gethash key hash))) (defun azos/nixpkgs-search () "Search for a nixpkgs package and show its info." (interactive) (let* ((pattern (read-string "Search pattern: ")) (json-output (shell-command-to-string (format "nix-search --json --max-results 50 '%s'" pattern))) (results (and json-output (not (string-empty-p json-output)) (azos/nixpkgs-parse-json-lines json-output))) (packages (delq nil (mapcar (lambda (item) (let ((pname (azos/nixpkgs-g "package_pname" item))) (when pname (cons pname item)))) results)))) (if packages (let* ((selected (completing-read "Package: " (mapcar #'car packages) nil t)) (package-data (cdr (assoc-string selected packages)))) (let ((buf (get-buffer-create "*nixpkgs-package-info*"))) (with-current-buffer buf (erase-buffer) (if package-data (progn (insert (format "Package: %s\n\n" selected)) (insert (format "Version: %s\n" (or (azos/nixpkgs-g "package_pversion" package-data) "unknown"))) (insert (format "Attr: %s\n" (or (azos/nixpkgs-g "package_attr_name" package-data) "unknown"))) (insert (format "\nDescription: %s\n" (or (azos/nixpkgs-g "package_description" package-data) "none"))) (let ((programs (azos/nixpkgs-g "package_programs" package-data))) (when (vectorp programs) (insert (format "\nPrograms: %s\n" (mapconcat #'identity (append programs nil) " "))))) (let ((homepage (let ((h (azos/nixpkgs-g "package_homepage" package-data))) (when (vectorp h) (aref h 0))))) (when homepage (insert (format "\nHomepage: %s\n" homepage)))) (let ((licenses (azos/nixpkgs-g "package_license" package-data))) (when (vectorp licenses) (insert (format "\nLicense: %s\n" (mapconcat (lambda (l) (or (azos/nixpkgs-g "fullName" l) "")) (append licenses nil) ", "))))) (insert (format "Package: %s\n\n" selected) "\nNo additional info available."))) (goto-char (point-min))) (display-buffer buf))) (message "No packages found matching '%s'" pattern)))) (define-key azos/global-minor-mode/open-keymap (kbd "n") 'azos/nixpkgs-search) #+end_src ** CalDAV / Org Sync #+begin_src emacs-lisp (require 'org-caldav) (defvar azos/lauretta/nextcloud-user "aner" "Nextcloud username for CalDAV sync.") (setq org-caldav-url "https://nextcloud.zakobar.com/remote.php/dav/calendars/1ddd03a6-4c2d-103c-9f7b-27b20313341d" org-caldav-calendar-id "personal" org-caldav-inbox "~/org/caldav-inbox.org" org-caldav-files '("~/org/todo.org") org-icalendar-timezone "Asia/Jerusalem") (defun azos/caldav-sync () "Sync org-caldav with Nextcloud calendar, reading password from pass." (interactive) (let* ((password (string-trim (shell-command-to-string "pass zakobar.com | head -1"))) (url-http-real-basic-auth-storage (list (list "nextcloud.zakobar.com:443" (cons azos/lauretta/nextcloud-user password))))) (org-caldav-sync))) (define-key azos/global-minor-mode/open-keymap (kbd "C") 'azos/caldav-sync) #+end_src ** Beacon Remote Jobs #+begin_src emacs-lisp (defvar azos/beacon/host "aner@192.168.1.200" "SSH connection for beacon machine.") (defvar azos/beacon/max-logs 5 "Number of most-recent logs to keep per project on beacon.") (defvar azos/beacon/keymap (make-sparse-keymap) "Keymap for beacon remote job commands (M-o b prefix).") (define-key azos/global-minor-mode/open-keymap (kbd "b") azos/beacon/keymap) (defun azos/beacon--project-root () (or (and (fboundp 'projectile-project-root) (projectile-project-root)) (error "Not in a projectile project"))) (defun azos/beacon--project-name (root) (file-name-nondirectory (directory-file-name root))) (defun azos/beacon--list-python-files (root) (split-string (shell-command-to-string (format "cd %s && git ls-files | grep '\\.py$'" (shell-quote-argument root))) "\n" t)) (defun azos/beacon--list-executables (root) (split-string (shell-command-to-string (format "cd %s && git ls-files | while IFS= read -r f; do [ -x \"$f\" ] && echo \"$f\"; done" (shell-quote-argument root))) "\n" t)) (defun azos/beacon--ssh-run (script) "Pipe SCRIPT as bash to beacon over SSH stdin. Error on non-zero exit." (with-temp-buffer (insert script) (let ((ret (call-process-region (point-min) (point-max) "ssh" nil t nil azos/beacon/host "bash"))) (unless (zerop ret) (error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string))))))) (defun azos/beacon--ssh-query (cmd &optional allow-exit-one) "Run CMD on beacon, return output string. Error on failure. With ALLOW-EXIT-ONE, exit code 1 is also treated as success (for tmux ls)." (with-temp-buffer (let ((ret (call-process "ssh" nil t nil azos/beacon/host cmd))) (unless (or (zerop ret) (and allow-exit-one (= ret 1))) (error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string))))) (buffer-string))) (defun azos/beacon--dispatch (root cmd label) "Sync ROOT to beacon and run CMD in project dir under direnv. LABEL names the tmux session and log file." (let* ((project-name (azos/beacon--project-name root)) (timestamp (format-time-string "%Y%m%d-%H%M%S")) (session (format "%s-%s-%s" project-name label timestamp)) (log-name (format "%s-%s.log" label timestamp))) (message "Syncing %s to beacon..." project-name) (azos/beacon--ssh-run (format "mkdir -p ~/beacon-projects/%s\n" project-name)) (with-temp-buffer (let ((ret (call-process-shell-command (format "bash -c %s" (shell-quote-argument (format "cd %s && git ls-files | rsync -avz --files-from=- . %s:~/beacon-projects/%s/" root azos/beacon/host project-name))) nil t nil))) (unless (zerop ret) (error "Beacon rsync failed (exit %d): %s" ret (string-trim (buffer-string)))))) (azos/beacon--ssh-run (format "RDIR=~/beacon-projects/%s LDIR=~/beacon-logs/%s mkdir -p \"$LDIR\" direnv allow \"$RDIR\" tmux new-session -d -s %s -- bash -c \"cd $RDIR && direnv exec . %s 2>&1 | tee $LDIR/%s\" " project-name project-name (shell-quote-argument session) cmd log-name)) (azos/beacon--ssh-run (format "find ~/beacon-logs/%s -name '*.log' -printf '%%T@ %%p\\n' 2>/dev/null | sort -rn | tail -n +%d | cut -d' ' -f2- | xargs -r rm -f\n" project-name (1+ azos/beacon/max-logs))) (message "Beacon job started: %s" session))) (defun azos/beacon/run-python () "Sync current project to beacon and run a selected Python file." (interactive) (let* ((root (azos/beacon--project-root)) (files (azos/beacon--list-python-files root)) (file (completing-read "Python file: " files nil t)) (label (file-name-sans-extension (file-name-nondirectory file)))) (azos/beacon--dispatch root (format "python3 %s" file) label))) (defun azos/beacon/run-exec () "Sync current project to beacon and run a selected executable." (interactive) (let* ((root (azos/beacon--project-root)) (files (azos/beacon--list-executables root)) (file (completing-read "Executable: " files nil t)) (label (file-name-sans-extension (file-name-nondirectory file)))) (azos/beacon--dispatch root (format "./%s" file) label))) (defun azos/beacon/run-command () "Sync current project to beacon and run an arbitrary command." (interactive) (let* ((root (azos/beacon--project-root)) (cmd (read-string "Command: ")) (label (replace-regexp-in-string "[^a-zA-Z0-9-]" "-" (substring cmd 0 (min 20 (length cmd)))))) (azos/beacon--dispatch root cmd label))) (defun azos/beacon/list-jobs () "Show all running beacon tmux sessions." (interactive) (async-shell-command (format "ssh %s 'tmux ls 2>/dev/null || echo \"No jobs running\"'" azos/beacon/host) "*beacon-jobs*")) (defun azos/beacon/kill-job () "Kill a beacon tmux session selected interactively." (interactive) (let* ((output (azos/beacon--ssh-query "tmux ls -F '#S' 2>/dev/null" t)) (sessions (split-string (string-trim output) "\n" t)) (session (completing-read "Kill job: " sessions nil t))) (azos/beacon--ssh-run (format "tmux kill-session -t %s\n" (shell-quote-argument session))) (message "Killed beacon job: %s" session))) (defun azos/beacon--tail-path (path) (async-shell-command (format "ssh %s %s" azos/beacon/host (shell-quote-argument (format "tail -f %s" path))) (format "*beacon-tail-%s*" (file-name-nondirectory path)))) (defun azos/beacon/tail-log (&optional arg) "Tail the most recent beacon log. With prefix ARG, prompt for a pattern then select from matches." (interactive "P") (let* ((sorted-output (azos/beacon--ssh-query "find ~/beacon-logs -name '*.log' -printf '%T@ %P\n' 2>/dev/null | sort -rn | cut -d' ' -f2-")) (logs (split-string (string-trim sorted-output) "\n" t))) (when (null logs) (error "No logs found on beacon")) (if (not arg) (azos/beacon--tail-path (format "~/beacon-logs/%s" (car logs))) (let* ((pattern (read-string "Log pattern: ")) (matches (seq-filter (lambda (l) (string-match-p pattern l)) logs)) (log (if (= (length matches) 1) (car matches) (completing-read "Tail log: " matches nil t)))) (azos/beacon--tail-path (format "~/beacon-logs/%s" log)))))) (define-key azos/beacon/keymap (kbd "p") #'azos/beacon/run-python) (define-key azos/beacon/keymap (kbd "e") #'azos/beacon/run-exec) (define-key azos/beacon/keymap (kbd "c") #'azos/beacon/run-command) (define-key azos/beacon/keymap (kbd "l") #'azos/beacon/list-jobs) (define-key azos/beacon/keymap (kbd "k") #'azos/beacon/kill-job) (define-key azos/beacon/keymap (kbd "t") #'azos/beacon/tail-log) #+end_src ** Roam Backup #+begin_src emacs-lisp (defun azos/roam/backup () "Commit and push ~/roam to the remote git repo." (interactive) (async-shell-command "roam-backup" "*roam-backup*")) (define-key azos/roam-keymap (kbd "B") #'azos/roam/backup) #+end_src ** Printing #+begin_src emacs-lisp (defun azos/print-buffer (&optional arg) "Print the current buffer. For image/PDF/doc buffers, sends the file path to lp directly. For text buffers, uses lpr-buffer. With C-u, interactively prompt for scaling, B&W, and orientation options." (interactive "P") (let* ((image-p (derived-mode-p 'image-mode 'pdf-view-mode 'doc-view-mode)) (scale (when arg (y-or-n-p "Fit to page? "))) (bw (when arg (y-or-n-p "Black and white? "))) (landscape (when (and arg image-p) (y-or-n-p "Landscape? "))) (lp-opts (concat (when scale " -o fit-to-page") (when bw " -o print-color-mode=monochrome") (when landscape " -o landscape")))) (if image-p (if buffer-file-name (progn (shell-command (concat "lp" lp-opts " " (shell-quote-argument buffer-file-name))) (message "Sent %s to printer%s%s%s." (file-name-nondirectory buffer-file-name) (if scale " [scaled]" "") (if bw " [B&W]" "") (if landscape " [landscape]" ""))) (error "Buffer has no associated file")) (let ((lpr-switches (append lpr-switches (when bw '("-o print-color-mode=monochrome"))))) (lpr-buffer))))) (define-key azos/global-minor-mode/open-keymap (kbd "P") #'azos/print-buffer) #+end_src * Provide #+begin_src emacs-lisp (provide 'azos-emacs-lauretta) #+end_src