#+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 ** 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-opencode-default-model-id "opencode/big-pickle") ;; (setq agent-shell-preferred-agent-config (agent-shell-opencode-make-agent-config)) #+end_src ** Headphones #+begin_src emacs-lisp (defun azos/connect-headphones () (interactive) (start-process-shell-command "connect-headphones" nil "azos-connect-headphones-whmx4000")) (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 (format "pass zakobar.com/users/%s" azos/lauretta/nextcloud-user)))) (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 * Provide #+begin_src emacs-lisp (provide 'azos-emacs-lauretta) #+end_src