From 104a7b07643182d53d0766e5cc53a666dcf2f13a Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Sat, 18 Jul 2026 17:53:32 -0400 Subject: [PATCH] feat: project-aware Hermes session integration - SPC p h: new session (prompts for title, captures ID, opens vterm) - SPC p H: resume menu (newest-first, completing-read) - SPC p l: link external session to project (browse or by ID) - hermes: org link type opens in vterm, not shell-mode - One-shot init message: "Init session for PROJECT. Reply ONLY with OK." - Regex fix: [0-9a-f_]+ for hex session IDs - .hermes-sessions dotfile at project root (git-tracked) - gortium/ensure-node-structure creates .hermes-sessions + gitignore whitelist - Refactored: gortium/hermes--cmd, gortium/hermes--project-info --- doom/.config/doom/README.org | 388 ++++++++++++++++++++---- doom/.config/doom/config.el | 361 +++++++++++++++++++--- doom/.config/doom/hermes_logo_ascii.png | Bin 0 -> 5508 bytes doom/.config/doom/nil | 33 -- 4 files changed, 650 insertions(+), 132 deletions(-) create mode 100644 doom/.config/doom/hermes_logo_ascii.png delete mode 100644 doom/.config/doom/nil diff --git a/doom/.config/doom/README.org b/doom/.config/doom/README.org index 28c9f9f..d841215 100644 --- a/doom/.config/doom/README.org +++ b/doom/.config/doom/README.org @@ -42,6 +42,24 @@ they are implemented. #+begin_src emacs-lisp ;;; $DOOMDIR/config.el -*- lexical-binding: t; -*- #+end_src +** Banner +#+begin_src emacs-lisp +;; 1. Set the image path +(setq fancy-splash-image (expand-file-name "hermes_logo_ascii.png" doom-user-dir)) + +;; 2. Define the quote function +(defun my-dashboard-quote () + (insert "\n" (+dashboard--center +dashboard--width "Simplicity is the ultimate sophistication.") "\n")) + +;; 3. Override the functions list using the correct variable from the docs +(after! doom-dashboard + (setq +dashboard-functions + '(+dashboard-widget-banner + my-dashboard-quote + +dashboard-widget-loaded + +dashboard-widget-shortmenu + +dashboard-widget-footer))) +#+end_src ** User Info @@ -110,7 +128,7 @@ No more auto-discovery of random =.git= repos. #+begin_src emacs-lisp (setq bookmark-save-flag 1) -(setq bookmark-default-file (expand-file-name "bookmarks" (expand-file-name "~/ExoKortex/2-Areas/Home-Infra/2-Assets/bookmark"))) +(setq bookmark-default-file (expand-file-name "~/ExoKortex/2-Areas/Home-Infra/2-Assets/bookmark")) #+end_src ** Buffer auto reload @@ -127,17 +145,7 @@ My current workflow consist in having the 3-5 files I work on open in vertical s #+begin_src emacs-lisp (map! :leader (:prefix ("w" . "window") - :desc "Minimize window" "O" #'minimize-window) - (:prefix ("z" . "Personal") - :desc "Hermes Agent" "h" #'gortium/hermes-vterm)) -#+end_src - -** Hermes remote shortcut - -#+begin_src emacs-lisp -(map! :leader - (:prefix ("z" . "Personal") - :desc "Hermes Agent" "h" #'gortium/hermes-vterm)) + :desc "Minimize window" "O" #'minimize-window)) #+end_src ** Ultra-scroll @@ -333,9 +341,11 @@ org-agenda-files (seq-uniq org-agenda-files) (setq org-agenda-exporter-settings '((ps-print-color-p 'nil))) (setq org-agenda-follow-mode nil - org-agenda-skip-deadline-if-done nil - org-agenda-skip-scheduled-if-done nil - org-agenda-skip-timestamp-if-done nil + org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done t + org-agenda-skip-timestamp-if-done t + org-agenda-skip-deadline-prewarning-if-scheduled t + org-agenda-skip-scheduled-if-deadline-is-shown t org-agenda-sticky t org-journal-enable-agenda-integration t org-log-done 'time @@ -726,6 +736,27 @@ org-agenda-files (seq-uniq org-agenda-files) (append '(("\\.x?html?\\'" . default)) (assq-delete-all "\\.x?html?\\'" org-file-apps)))) #+end_src +** Calendar +#+begin_src emacs-lisp +(after! calendar + ;; Define a function to open org-roam daily note for selected date in calendar + (defun gortium/org-roam-dailies-open-at-point () + "Open org-roam daily note for date at point in calendar." + (interactive) + (let ((date (calendar-cursor-to-date))) + (org-roam-dailies-goto-date date))) + ) + +;; Bind RET in calendar mode to open daily note instead of default exit +(evil-define-key 'normal calendar-mode-map + (kbd "RET") #'gortium/org-roam-dailies-open-at-point) + +(after! org + (define-key org-read-date-minibuffer-local-map (kbd "M-h") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-j") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-k") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-l") (kbd "S-"))) +#+end_src ** +Org Superstar mode+ org-modern instead @@ -1205,19 +1236,6 @@ Handles org-clock and context link capture for tasks." (gortium/connecteam-clock-out gortium/connecteam-clock-id)))) (add-hook 'org-clock-out-hook #'gortium/org-clock-out-from-connecteam) - -(after! calendar - ;; Define a function to open org-roam daily note for selected date in calendar - (defun gortium/org-roam-dailies-open-at-point () - "Open org-roam daily note for date at point in calendar." - (interactive) - (let ((date (calendar-cursor-to-date))) - (org-roam-dailies-goto-date date))) - ) - -;; Bind RET in calendar mode to open daily note instead of default exit -(evil-define-key 'normal calendar-mode-map - (kbd "RET") #'gortium/org-roam-dailies-open-at-point) #+end_src *** Old config @@ -1949,23 +1967,260 @@ Org-AI (org-ai-install-yasnippets)) ; if you are using yasnippet and want `ai` snippets #+end_src -** +Hermes Agent+ -Hermes Agent runs on lazyworkhorse server. Launched via SSH in a vterm buffer. +** +Hermes Agent — Project Session Tracking+ + +Hermes chat sessions are automatically linked to ExoKortex projects. +All logic runs inside Emacs — no bash aliases, no wrapper scripts. + +| Binding | Function | +|-------------+---------------------------------------| +| ~SPC p h~ | New session (prompts for title) | +| ~C-u SPC p h~ | New remote session (SSH + Docker) | +| ~SPC p H~ | Browse/resume past sessions (menu) | +| ~hermes:~ | Org link type — click to resume | + +Session ID is captured at session *start* (before vterm opens), so +even if the terminal window is closed immediately, the session is +already saved to ~.hermes-sessions~ at the project root. #+begin_src emacs-lisp -(after! vterm - (defun gortium/hermes-vterm () - "Launch Hermes Agent in vterm via SSH to lazyworkhorse (CLI mode)." - (interactive) - (let ((buf (get-buffer-create "*hermes*"))) - (switch-to-buffer buf) - (unless (and (eq major-mode 'vterm-mode) - (get-buffer-process buf)) - (vterm-mode) - (let ((proc (get-buffer-process buf))) - (when proc - (process-send-string - proc "ssh -t lazyworkhorse 'docker exec -it hermes /opt/hermes/.venv/bin/hermes'\n"))))))) +;;; ==================================================================== +;;; Hermes Session ↔ Project Integration +;;; ==================================================================== + +;; Configuration (customize per-host) +(defvar gortium/hermes-remote-host "lazyworkhorse" + "SSH host for remote Hermes. nil = use local Hermes.") +(defvar gortium/hermes-remote-docker "hermes" + "Docker container name for remote Hermes.") +(defvar gortium/hermes-remote-path + "/opt/hermes/.venv/bin/hermes" + "Path to the hermes executable inside the Docker container.") + +;; ------------------------------------------------------------------- +;; Org link type: [[hermes:20260718_162500_a1b2c3][Resume — Title]] +;; ------------------------------------------------------------------- +(org-link-set-parameters "hermes" + :follow (lambda (id) + (gortium/hermes--open-vterm + (format "hermes chat --resume %s" id) + "*hermes*")) + :help-echo "Resume this Hermes chat session in vterm") + +;; ------------------------------------------------------------------- +;; Helpers — build commands, detect project +;; ------------------------------------------------------------------- + +(defun gortium/hermes--remote-prefix (&optional host) + "Build SSH/Docker prefix for running Hermes commands remotely." + (let ((h (or host gortium/hermes-remote-host))) + (format "ssh %s docker exec %s %s" h + gortium/hermes-remote-docker + gortium/hermes-remote-path))) + +(defun gortium/hermes--cmd (subcmd &optional remote-host) + "Prefix SUBCMD with local 'hermes' or remote SSH/Docker." + (if remote-host + (format "%s %s" (gortium/hermes--remote-prefix remote-host) subcmd) + (format "hermes %s" subcmd))) + +(defun gortium/hermes--project-info () + "Return (ROOT SESSIONS-FILE NAME) or signal error if not in project." + (let ((root (projectile-project-root))) + (if root + (list root + (expand-file-name ".hermes-sessions" root) + (file-name-nondirectory (directory-file-name root))) + (user-error "Not in a Projectile project")))) + +;; ------------------------------------------------------------------- +;; Capture session ID at session start (one-shot, before vterm opens) +;; ------------------------------------------------------------------- +(defun gortium/hermes--capture-session-id (&optional remote-host) + "Create a quick Hermes session and return its ID. +Sends a meaningful init message so history starts with project context. +session_id: is on stderr; 2>&1 merges it so shell-command-to-string catches it." + (let* ((proj (nth 2 (gortium/hermes--project-info))) + (cmd (gortium/hermes--cmd + (format "chat -q \"Init session for %s. Reply ONLY with OK.\" --quiet 2>&1" proj) + remote-host)) + (output (shell-command-to-string cmd))) + (if (string-match "session_id: \\([0-9a-f_]+\\)" output) + (match-string 1 output) + (user-error "Could not capture Hermes session ID")))) + +;; ------------------------------------------------------------------- +;; Append session entry to .hermes-sessions +;; ------------------------------------------------------------------- +(defun gortium/hermes-append-session (sessions-file id title) + "Append session ID and TITLE to SESSIONS-FILE (org format)." + (let ((proj (file-name-nondirectory + (directory-file-name + (file-name-directory sessions-file))))) + (unless (file-exists-p sessions-file) + (with-temp-buffer + (insert "# -*- mode: org -*-\n" + "#+TITLE: Sessions — " proj "\n\n* Sessions\n\n") + (write-region (point-min) (point-max) sessions-file nil 'silent))) + (with-temp-buffer + (insert (format "\n** %s — %s\n:PROPERTIES:\n:HERMES_SESSION: %s\n:END:\n[[hermes:%s][Resume — %s]]\n\n" + (format-time-string "%Y-%m-%d %H:%M") title + id id title)) + (write-region (point-min) (point-max) sessions-file t 'silent)))) + +;; ------------------------------------------------------------------- +;; Rename session globally +;; ------------------------------------------------------------------- +(defun gortium/hermes--rename-session (id fancy-title &optional remote-host) + "Rename a Hermes session globally (adds project prefix)." + (shell-command-to-string + (gortium/hermes--cmd + (format "sessions rename %s \"%s\"" id fancy-title) remote-host))) + +;; ------------------------------------------------------------------- +;; Build launch command for interactive vterm +;; ------------------------------------------------------------------- +(defun gortium/hermes--launch-cmd (id &optional remote-host) + "Build shell command to resume session ID in a terminal. +Remote needs ssh -t + docker exec -it (PTY required)." + (if remote-host + (format "ssh -t %s docker exec -it %s %s chat --resume %s" + remote-host gortium/hermes-remote-docker + gortium/hermes-remote-path id) + (format "hermes chat --resume %s" id))) + +;; ------------------------------------------------------------------- +;; Open vterm buffer and run command +;; ------------------------------------------------------------------- +(defun gortium/hermes--open-vterm (command &optional name) + "Open a vterm buffer and run COMMAND inside it." + (let ((buf (get-buffer-create (or name "*hermes*")))) + (switch-to-buffer buf) + (unless (and (eq major-mode 'vterm-mode) + (get-buffer-process buf)) + (vterm-mode) + (let ((proc (get-buffer-process buf))) + (when proc (process-send-string proc (concat command "\n"))))))) + +;; ------------------------------------------------------------------- +;; Start a new Hermes session (with title prompt) +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-start (title &optional remote-host) + "Start a new Hermes session for the current project. +With prefix arg, prompt for remote SSH host (default: lazyworkhorse)." + (interactive + (let* ((remote (when current-prefix-arg + (let ((h (read-string + "Remote host [lazyworkhorse]: " nil nil "lazyworkhorse"))) + (unless (string-empty-p h) h)))) + (title (read-string "Session title: "))) + (list title remote))) + (pcase-let ((`(,root ,sfile ,name) (gortium/hermes--project-info))) + (let ((id (gortium/hermes--capture-session-id remote-host))) + (gortium/hermes--rename-session id (format "[%s] %s" name title) remote-host) + (gortium/hermes-append-session sfile id title) + (gortium/hermes--open-vterm + (gortium/hermes--launch-cmd id remote-host) + (format "*hermes-%s*" name))))) + +;; ------------------------------------------------------------------- +;; Browse / resume past sessions (completing-read menu) +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-resume () + "Browse and resume a past Hermes session from the current project." + (interactive) + (pcase-let ((`(,root ,sfile ,_name) (gortium/hermes--project-info))) + (if (not (file-exists-p sfile)) + (user-error (format "No .hermes-sessions in %s" root)) + (let (sessions current) + (with-temp-buffer + (insert-file-contents sfile) + (while (not (eobp)) + (forward-line 1) + (cond + ((looking-at "^\\*\\* \\([^—]+\\) — \\(.+\\)$") + (setq current (list :date (string-trim (match-string 1)) + :title (match-string 2) + :id nil))) + ((and current (looking-at "^:HERMES_SESSION: \\(.+\\)$")) + (plist-put current :id (match-string 1)) + (push current sessions) + (setq current nil))))) + ;; sessions is in push-order = newest first + (if (null sessions) + (message "No Hermes sessions recorded for this project.") + (let* ((items (mapcar (lambda (s) + (format "%s — %s" + (plist-get s :date) + (plist-get s :title))) + sessions)) + (choice (completing-read "Resume Hermes session: " items nil t)) + (idx (cl-position choice items :test #'string=)) + (session (nth idx sessions)) + (id (plist-get session :id))) + (gortium/hermes--open-vterm + (format "hermes chat --resume %s" id)))))))) + +;; ------------------------------------------------------------------- +;; Link external session to current project +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-link-session (session-id title) + "Link an existing Hermes session to the current project. +If SESSION-ID is empty, show a menu of recent sessions to browse." + (interactive + (let ((id (read-string "Session ID (empty to browse recent): "))) + (if (string-empty-p id) + (let* ((output (shell-command-to-string "hermes sessions list --limit 50 2>&1")) + (lines (split-string output "\n" t)) + (data-lines (nthcdr 2 lines)) ; skip header + separator + (id-re "[0-9]\\{8\\}_[0-9]\\{6\\}_[a-f0-9]\\{6\\}") + (items (delq nil + (mapcar (lambda (line) + (when (string-match (concat id-re "$") line) + (let* ((id (match-string 0 line)) + (before-id (substring line 0 (match-beginning 0))) + (full-preview (string-trim + (replace-regexp-in-string + "\\s-+[a-z][a-z0-9]*\\s-*$" "" before-id)))) + (cons full-preview id))) + data-lines)) + (titles (delq nil + (mapcar (lambda (line) + (when (string-match (concat id-re "$") line) + (let* ((id (match-string 0 line)) + (before-id (substring line 0 (match-beginning 0))) + (full (string-trim + (replace-regexp-in-string + "\\s-+[a-z][a-z0-9]*\\s-*$" "" before-id))) + (clean (string-trim + (replace-regexp-in-string + "\\s-+\\(just now\\|[0-9]+[mhd] ago\\|[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)$" + "" full)))) + (cons id clean))) + data-lines)))) + (if (null items) + (user-error "No recent sessions found") + (let* ((vertico-sort-function 'identity) ; keep vertico, no re-sort + (choice (completing-read "Session: " items nil t)) + (sid (cdr (assoc choice items))) + (default-title (cdr (assoc sid titles)))) + (list sid (read-string "Title: " default-title))))) + (list id (read-string "Title: "))))) + (pcase-let ((`(,root ,sfile ,name) (gortium/hermes--project-info))) + (gortium/hermes--rename-session id (format "[%s] %s" name title)) + (gortium/hermes-append-session sfile id title) + (message "Session %s linked to project %s as \"%s\"" id name title))) + +;; ------------------------------------------------------------------- +;; Keybindings (under SPC p) +;; ------------------------------------------------------------------- +(map! :leader + :desc "New Hermes session" "p h" #'gortium/hermes-start + :desc "Resume Hermes session" "p H" #'gortium/hermes-resume + :desc "Link session to project" "p l" #'gortium/hermes-link-session) #+end_src * Tramp @@ -2043,7 +2298,7 @@ Recently added the quick access entries. Game changer. ;; Use `p` to open the yank menu (map! :after dirvish-yank :map dirvish-mode-map - :n "p" #'dirvish-yank-menu) + :n "P" #'dirvish-yank-menu) (map! :after dirvish :leader @@ -2466,6 +2721,27 @@ only injects when the project's context-tag matches current mu4e context." (emms-player-set emms-player-mpv 'regex (rx (or (: "http" (? "s") "://" (* nonl) "youtube.com" (* nonl)) (: "http" (? "s") "://" (* nonl) "youtu.be" (* nonl)))))) + +(defun my-elfeed-tube-add-to-emms () + "Add the currently selected Elfeed video(s) to the EMMS playlist." + (interactive) + (let ((entries (if (eq major-mode 'elfeed-show-mode) + (list elfeed-show-entry) + (elfeed-search-selected)))) + (dolist (entry entries) + (let ((link (elfeed-entry-link entry))) + (if link + (progn + (emms-add-url link) + (message "Queued in EMMS: %s" link)) + (message "No link found for this entry.")))))) + +;; Bind the function to 'e' in both elfeed views +(map! :map elfeed-search-mode-map + :n "e" #'my-elfeed-tube-add-to-emms) + +(map! :map elfeed-show-mode-map + :n "e" #'my-elfeed-tube-add-to-emms) #+end_src * EAF #+begin_src emacs-lisp @@ -3291,7 +3567,7 @@ Suppresses all logging, notes, and custom odometer hooks." (delete-window old-win))))))) #+end_src -** ExoKortex System +** ExoKortex File System #+begin_src emacs-lisp ;;; ==================================================================== @@ -3324,7 +3600,7 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) ;; 2. Enforce text-only gitignore rules if absent (unless (file-exists-p gitignore-file) (with-temp-file gitignore-file - (insert "# Local Node Rule: Track text layers only\n*\n!.gitignore\n!.projectile\n!.dir-locals.el\n!.bookmarks\n!1-Plan/\n!1-Plan/**\n!3-Notes/\n!3-Notes/**\n"))) + (insert "# Local Node Rule: Track text layers only\n*\n!.gitignore\n!.projectile\n!.dir-locals.el\n!.bookmarks\n!.hermes-sessions\n!1-Plan/\n!1-Plan/**\n!3-Notes/\n!3-Notes/**\n"))) ;; 3. Build index Org block if absent (unless (file-exists-p org-file) @@ -3369,6 +3645,12 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) (prin1 vars (current-buffer)) (princ "))" (current-buffer))))))) + ;; Create .hermes-sessions skeleton + (let ((hs-file (expand-file-name ".hermes-sessions" node-dir))) + (unless (file-exists-p hs-file) + (with-temp-file hs-file + (insert (format "# -*- mode: org -*-\n#+TITLE: Sessions — %s\n\n* Sessions\n\n" node-name))))) + ;; Mark these directory-local variables as safe (put 'gortium/project-tag 'safe-local-variable #'stringp) (put 'gortium/context-tag 'safe-local-variable #'stringp) @@ -3381,14 +3663,14 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) (shell-command "git init") (let ((add-files ".gitignore 1-Plan/ 3-Notes/")) (when project-tag - (setq add-files (concat add-files " .projectile .dir-locals.el"))) + (setq add-files (concat add-files " .projectile .dir-locals.el .hermes-sessions"))) (shell-command (concat "git add -f " add-files))) (shell-command "git commit -m 'System: Initialize node repository'")) ;; existing repo: add new marker files if they exist (let ((add-files ".gitignore 1-Plan/ 3-Notes/")) (when project-tag - (setq add-files (concat add-files " .projectile .dir-locals.el"))) + (setq add-files (concat add-files " .projectile .dir-locals.el .hermes-sessions"))) (shell-command (concat "git add -f " add-files))))) ;; Pass the target file location back up to the caller @@ -3432,14 +3714,14 @@ Optional PROJECT-TAG and CONTEXT-TAG can be given to create .projectile and .dir dired-elements)))) (dirs (or marked-dirs (let* ((cursor-path (when (derived-mode-p 'dired-mode) - (dired-get-filename nil t))) + (dired-get-filename nil t))) (initial-path (cond ((and cursor-path (file-directory-p cursor-path)) cursor-path) (cursor-path (file-name-directory cursor-path)) (t default-directory)))) - (list (read-directory-name "Directory to convert: " nil initial-path t initial-path))))) + (list (read-directory-name "Directory to convert: " initial-path initial-path t))))) (tag (read-string "Project tag (blank to skip): ")) (ctx (read-string "Context tag (or blank): "))) (list dirs tag ctx))) @@ -3449,9 +3731,9 @@ Optional PROJECT-TAG and CONTEXT-TAG can be given to create .projectile and .dir (dolist (dir node-dirs) (let* ((node-dir (expand-file-name dir)) (org-file (gortium/ensure-node-structure node-dir - "Audit existing assets and reconcile tracking nodes" - (unless (string-empty-p project-tag) project-tag) - (unless (string-empty-p context-tag) context-tag)))) + "Audit existing assets and reconcile tracking nodes" + (unless (string-empty-p project-tag) project-tag) + (unless (string-empty-p context-tag) context-tag)))) (setq last-org-file org-file) (cl-incf count))) diff --git a/doom/.config/doom/config.el b/doom/.config/doom/config.el index 5e75c09..0ec798b 100644 --- a/doom/.config/doom/config.el +++ b/doom/.config/doom/config.el @@ -1,5 +1,21 @@ ;;; $DOOMDIR/config.el -*- lexical-binding: t; -*- +;; 1. Set the image path +(setq fancy-splash-image (expand-file-name "hermes_logo_ascii.png" doom-user-dir)) + +;; 2. Define the quote function +(defun my-dashboard-quote () + (insert "\n" (+dashboard--center +dashboard--width "Simplicity is the ultimate sophistication.") "\n")) + +;; 3. Override the functions list using the correct variable from the docs +(after! doom-dashboard + (setq +dashboard-functions + '(+dashboard-widget-banner + my-dashboard-quote + +dashboard-widget-loaded + +dashboard-widget-shortmenu + +dashboard-widget-footer))) + (setq user-full-name "Thierry Pouplier" user-mail-address "tpouplier@tdnde.com" user-mail-address "tpouplier@tdnde.com") @@ -20,19 +36,13 @@ (setq projectile-auto-discover nil)) (setq bookmark-save-flag 1) -(setq bookmark-default-file (expand-file-name "bookmarks" (expand-file-name "~/ExoKortex/2-Areas/Home-Infra/2-Assets/bookmark"))) +(setq bookmark-default-file (expand-file-name "~/ExoKortex/2-Areas/Home-Infra/2-Assets/bookmark")) (setq global-auto-revert-mode 1) (map! :leader (:prefix ("w" . "window") - :desc "Minimize window" "O" #'minimize-window) - (:prefix ("z" . "Personal") - :desc "Hermes Agent" "h" #'gortium/hermes-vterm)) - -(map! :leader - (:prefix ("z" . "Personal") - :desc "Hermes Agent" "h" #'gortium/hermes-vterm)) + :desc "Minimize window" "O" #'minimize-window)) (use-package! ultra-scroll :init @@ -190,9 +200,11 @@ org-agenda-files (seq-uniq org-agenda-files) (setq org-agenda-exporter-settings '((ps-print-color-p 'nil))) (setq org-agenda-follow-mode nil - org-agenda-skip-deadline-if-done nil - org-agenda-skip-scheduled-if-done nil - org-agenda-skip-timestamp-if-done nil + org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done t + org-agenda-skip-timestamp-if-done t + org-agenda-skip-deadline-prewarning-if-scheduled t + org-agenda-skip-scheduled-if-deadline-is-shown t org-agenda-sticky t org-journal-enable-agenda-integration t org-log-done 'time @@ -567,6 +579,25 @@ org-agenda-files (seq-uniq org-agenda-files) (append '(("\\.x?html?\\'" . default)) (assq-delete-all "\\.x?html?\\'" org-file-apps)))) +(after! calendar + ;; Define a function to open org-roam daily note for selected date in calendar + (defun gortium/org-roam-dailies-open-at-point () + "Open org-roam daily note for date at point in calendar." + (interactive) + (let ((date (calendar-cursor-to-date))) + (org-roam-dailies-goto-date date))) + ) + +;; Bind RET in calendar mode to open daily note instead of default exit +(evil-define-key 'normal calendar-mode-map + (kbd "RET") #'gortium/org-roam-dailies-open-at-point) + +(after! org + (define-key org-read-date-minibuffer-local-map (kbd "M-h") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-j") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-k") (kbd "S-")) + (define-key org-read-date-minibuffer-local-map (kbd "M-l") (kbd "S-"))) + (after! grip-mode (setq grip-binary-path "/home/tpouplier/.local/bin/grip") ) @@ -780,19 +811,6 @@ Handles org-clock and context link capture for tasks." (add-hook 'org-clock-out-hook #'gortium/org-clock-out-from-connecteam) -(after! calendar - ;; Define a function to open org-roam daily note for selected date in calendar - (defun gortium/org-roam-dailies-open-at-point () - "Open org-roam daily note for date at point in calendar." - (interactive) - (let ((date (calendar-cursor-to-date))) - (org-roam-dailies-goto-date date))) - ) - -;; Bind RET in calendar mode to open daily note instead of default exit -(evil-define-key 'normal calendar-mode-map - (kbd "RET") #'gortium/org-roam-dailies-open-at-point) - (after! dap-mode (require 'dap-python) (setq dap-python-debugger 'debugpy) @@ -1162,19 +1180,243 @@ DIFF: :desc "GPT generate commit" "g" #'gortium/magit-gptel-generate-commit) -(after! vterm - (defun gortium/hermes-vterm () - "Launch Hermes Agent in vterm via SSH to lazyworkhorse (CLI mode)." - (interactive) - (let ((buf (get-buffer-create "*hermes*"))) - (switch-to-buffer buf) - (unless (and (eq major-mode 'vterm-mode) - (get-buffer-process buf)) - (vterm-mode) - (let ((proc (get-buffer-process buf))) - (when proc - (process-send-string - proc "ssh -t lazyworkhorse 'docker exec -it hermes /opt/hermes/.venv/bin/hermes'\n"))))))) +;;; ==================================================================== +;;; Hermes Session ↔ Project Integration +;;; ==================================================================== + +;; Configuration (customize per-host) +(defvar gortium/hermes-remote-host "lazyworkhorse" + "SSH host for remote Hermes. nil = use local Hermes.") +(defvar gortium/hermes-remote-docker "hermes" + "Docker container name for remote Hermes.") +(defvar gortium/hermes-remote-path + "/opt/hermes/.venv/bin/hermes" + "Path to the hermes executable inside the Docker container.") + +;; ------------------------------------------------------------------- +;; Org link type: [[hermes:20260718_162500_a1b2c3][Resume — Title]] +;; ------------------------------------------------------------------- +(org-link-set-parameters "hermes" + :follow (lambda (id) + (gortium/hermes--open-vterm + (format "hermes chat --resume %s" id) + "*hermes*")) + :help-echo "Resume this Hermes chat session in vterm") + +;; ------------------------------------------------------------------- +;; Helpers — build commands, detect project +;; ------------------------------------------------------------------- + +(defun gortium/hermes--remote-prefix (&optional host) + "Build SSH/Docker prefix for running Hermes commands remotely." + (let ((h (or host gortium/hermes-remote-host))) + (format "ssh %s docker exec %s %s" h + gortium/hermes-remote-docker + gortium/hermes-remote-path))) + +(defun gortium/hermes--cmd (subcmd &optional remote-host) + "Prefix SUBCMD with local 'hermes' or remote SSH/Docker." + (if remote-host + (format "%s %s" (gortium/hermes--remote-prefix remote-host) subcmd) + (format "hermes %s" subcmd))) + +(defun gortium/hermes--project-info () + "Return (ROOT SESSIONS-FILE NAME) or signal error if not in project." + (let ((root (projectile-project-root))) + (if root + (list root + (expand-file-name ".hermes-sessions" root) + (file-name-nondirectory (directory-file-name root))) + (user-error "Not in a Projectile project")))) + +;; ------------------------------------------------------------------- +;; Capture session ID at session start (one-shot, before vterm opens) +;; ------------------------------------------------------------------- +(defun gortium/hermes--capture-session-id (&optional remote-host) + "Create a quick Hermes session and return its ID. +Sends a meaningful init message so history starts with project context. +session_id: is on stderr; 2>&1 merges it so shell-command-to-string catches it." + (let* ((proj (nth 2 (gortium/hermes--project-info))) + (cmd (gortium/hermes--cmd + (format "chat -q \"Init session for %s. Reply ONLY with OK.\" --quiet 2>&1" proj) + remote-host)) + (output (shell-command-to-string cmd))) + (if (string-match "session_id: \\([0-9a-f_]+\\)" output) + (match-string 1 output) + (user-error "Could not capture Hermes session ID")))) + +;; ------------------------------------------------------------------- +;; Append session entry to .hermes-sessions +;; ------------------------------------------------------------------- +(defun gortium/hermes-append-session (sessions-file id title) + "Append session ID and TITLE to SESSIONS-FILE (org format)." + (let ((proj (file-name-nondirectory + (directory-file-name + (file-name-directory sessions-file))))) + (unless (file-exists-p sessions-file) + (with-temp-buffer + (insert "# -*- mode: org -*-\n" + "#+TITLE: Sessions — " proj "\n\n* Sessions\n\n") + (write-region (point-min) (point-max) sessions-file nil 'silent))) + (with-temp-buffer + (insert (format "\n** %s — %s\n:PROPERTIES:\n:HERMES_SESSION: %s\n:END:\n[[hermes:%s][Resume — %s]]\n\n" + (format-time-string "%Y-%m-%d %H:%M") title + id id title)) + (write-region (point-min) (point-max) sessions-file t 'silent)))) + +;; ------------------------------------------------------------------- +;; Rename session globally +;; ------------------------------------------------------------------- +(defun gortium/hermes--rename-session (id fancy-title &optional remote-host) + "Rename a Hermes session globally (adds project prefix)." + (shell-command-to-string + (gortium/hermes--cmd + (format "sessions rename %s \"%s\"" id fancy-title) remote-host))) + +;; ------------------------------------------------------------------- +;; Build launch command for interactive vterm +;; ------------------------------------------------------------------- +(defun gortium/hermes--launch-cmd (id &optional remote-host) + "Build shell command to resume session ID in a terminal. +Remote needs ssh -t + docker exec -it (PTY required)." + (if remote-host + (format "ssh -t %s docker exec -it %s %s chat --resume %s" + remote-host gortium/hermes-remote-docker + gortium/hermes-remote-path id) + (format "hermes chat --resume %s" id))) + +;; ------------------------------------------------------------------- +;; Open vterm buffer and run command +;; ------------------------------------------------------------------- +(defun gortium/hermes--open-vterm (command &optional name) + "Open a vterm buffer and run COMMAND inside it." + (let ((buf (get-buffer-create (or name "*hermes*")))) + (switch-to-buffer buf) + (unless (and (eq major-mode 'vterm-mode) + (get-buffer-process buf)) + (vterm-mode) + (let ((proc (get-buffer-process buf))) + (when proc (process-send-string proc (concat command "\n"))))))) + +;; ------------------------------------------------------------------- +;; Start a new Hermes session (with title prompt) +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-start (title &optional remote-host) + "Start a new Hermes session for the current project. +With prefix arg, prompt for remote SSH host (default: lazyworkhorse)." + (interactive + (let* ((remote (when current-prefix-arg + (let ((h (read-string + "Remote host [lazyworkhorse]: " nil nil "lazyworkhorse"))) + (unless (string-empty-p h) h)))) + (title (read-string "Session title: "))) + (list title remote))) + (pcase-let ((`(,root ,sfile ,name) (gortium/hermes--project-info))) + (let ((id (gortium/hermes--capture-session-id remote-host))) + (gortium/hermes--rename-session id (format "[%s] %s" name title) remote-host) + (gortium/hermes-append-session sfile id title) + (gortium/hermes--open-vterm + (gortium/hermes--launch-cmd id remote-host) + (format "*hermes-%s*" name))))) + +;; ------------------------------------------------------------------- +;; Browse / resume past sessions (completing-read menu) +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-resume () + "Browse and resume a past Hermes session from the current project." + (interactive) + (pcase-let ((`(,root ,sfile ,_name) (gortium/hermes--project-info))) + (if (not (file-exists-p sfile)) + (user-error (format "No .hermes-sessions in %s" root)) + (let (sessions current) + (with-temp-buffer + (insert-file-contents sfile) + (while (not (eobp)) + (forward-line 1) + (cond + ((looking-at "^\\*\\* \\([^—]+\\) — \\(.+\\)$") + (setq current (list :date (string-trim (match-string 1)) + :title (match-string 2) + :id nil))) + ((and current (looking-at "^:HERMES_SESSION: \\(.+\\)$")) + (plist-put current :id (match-string 1)) + (push current sessions) + (setq current nil))))) + ;; sessions is in push-order = newest first + (if (null sessions) + (message "No Hermes sessions recorded for this project.") + (let* ((items (mapcar (lambda (s) + (format "%s — %s" + (plist-get s :date) + (plist-get s :title))) + sessions)) + (choice (completing-read "Resume Hermes session: " items nil t)) + (idx (cl-position choice items :test #'string=)) + (session (nth idx sessions)) + (id (plist-get session :id))) + (gortium/hermes--open-vterm + (format "hermes chat --resume %s" id)))))))) + +;; ------------------------------------------------------------------- +;; Link external session to current project +;; ------------------------------------------------------------------- +;;;###autoload +(defun gortium/hermes-link-session (session-id title) + "Link an existing Hermes session to the current project. +If SESSION-ID is empty, show a menu of recent sessions to browse." + (interactive + (let ((id (read-string "Session ID (empty to browse recent): "))) + (if (string-empty-p id) + (let* ((output (shell-command-to-string "hermes sessions list --limit 50 2>&1")) + (lines (split-string output "\n" t)) + (data-lines (nthcdr 2 lines)) ; skip header + separator + (id-re "[0-9]\\{8\\}_[0-9]\\{6\\}_[a-f0-9]\\{6\\}") + (items (delq nil + (mapcar (lambda (line) + (when (string-match (concat id-re "$") line) + (let* ((id (match-string 0 line)) + (before-id (substring line 0 (match-beginning 0))) + (full-preview (string-trim + (replace-regexp-in-string + "\\s-+[a-z][a-z0-9]*\\s-*$" "" before-id)))) + (cons full-preview id))) + data-lines)) + (titles (delq nil + (mapcar (lambda (line) + (when (string-match (concat id-re "$") line) + (let* ((id (match-string 0 line)) + (before-id (substring line 0 (match-beginning 0))) + (full (string-trim + (replace-regexp-in-string + "\\s-+[a-z][a-z0-9]*\\s-*$" "" before-id))) + (clean (string-trim + (replace-regexp-in-string + "\\s-+\\(just now\\|[0-9]+[mhd] ago\\|[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)$" + "" full)))) + (cons id clean))) + data-lines)))) + (if (null items) + (user-error "No recent sessions found") + (let* ((vertico-sort-function 'identity) ; keep vertico, no re-sort + (choice (completing-read "Session: " items nil t)) + (sid (cdr (assoc choice items))) + (default-title (cdr (assoc sid titles)))) + (list sid (read-string "Title: " default-title))))) + (list id (read-string "Title: "))))) + (pcase-let ((`(,root ,sfile ,name) (gortium/hermes--project-info))) + (gortium/hermes--rename-session id (format "[%s] %s" name title)) + (gortium/hermes-append-session sfile id title) + (message "Session %s linked to project %s as \"%s\"" id name title))) + +;; ------------------------------------------------------------------- +;; Keybindings (under SPC p) +;; ------------------------------------------------------------------- +(map! :leader + :desc "New Hermes session" "p h" #'gortium/hermes-start + :desc "Resume Hermes session" "p H" #'gortium/hermes-resume + :desc "Link session to project" "p l" #'gortium/hermes-link-session) ;; set `tramp-direct-async-process' locally in all ssh connections (connection-local-set-profile-variables @@ -1240,7 +1482,7 @@ DIFF: ;; Use `p` to open the yank menu (map! :after dirvish-yank :map dirvish-mode-map - :n "p" #'dirvish-yank-menu) + :n "P" #'dirvish-yank-menu) (map! :after dirvish :leader @@ -1620,6 +1862,27 @@ only injects when the project's context-tag matches current mu4e context." (rx (or (: "http" (? "s") "://" (* nonl) "youtube.com" (* nonl)) (: "http" (? "s") "://" (* nonl) "youtu.be" (* nonl)))))) +(defun my-elfeed-tube-add-to-emms () + "Add the currently selected Elfeed video(s) to the EMMS playlist." + (interactive) + (let ((entries (if (eq major-mode 'elfeed-show-mode) + (list elfeed-show-entry) + (elfeed-search-selected)))) + (dolist (entry entries) + (let ((link (elfeed-entry-link entry))) + (if link + (progn + (emms-add-url link) + (message "Queued in EMMS: %s" link)) + (message "No link found for this entry.")))))) + +;; Bind the function to 'e' in both elfeed views +(map! :map elfeed-search-mode-map + :n "e" #'my-elfeed-tube-add-to-emms) + +(map! :map elfeed-show-mode-map + :n "e" #'my-elfeed-tube-add-to-emms) + (use-package! eaf :load-path "~/.config/emacs/.local/straight/repos/emacs-application-framework" :config @@ -2408,7 +2671,7 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) ;; 2. Enforce text-only gitignore rules if absent (unless (file-exists-p gitignore-file) (with-temp-file gitignore-file - (insert "# Local Node Rule: Track text layers only\n*\n!.gitignore\n!.projectile\n!.dir-locals.el\n!.bookmarks\n!1-Plan/\n!1-Plan/**\n!3-Notes/\n!3-Notes/**\n"))) + (insert "# Local Node Rule: Track text layers only\n*\n!.gitignore\n!.projectile\n!.dir-locals.el\n!.bookmarks\n!.hermes-sessions\n!1-Plan/\n!1-Plan/**\n!3-Notes/\n!3-Notes/**\n"))) ;; 3. Build index Org block if absent (unless (file-exists-p org-file) @@ -2453,6 +2716,12 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) (prin1 vars (current-buffer)) (princ "))" (current-buffer))))))) + ;; Create .hermes-sessions skeleton + (let ((hs-file (expand-file-name ".hermes-sessions" node-dir))) + (unless (file-exists-p hs-file) + (with-temp-file hs-file + (insert (format "# -*- mode: org -*-\n#+TITLE: Sessions — %s\n\n* Sessions\n\n" node-name))))) + ;; Mark these directory-local variables as safe (put 'gortium/project-tag 'safe-local-variable #'stringp) (put 'gortium/context-tag 'safe-local-variable #'stringp) @@ -2465,14 +2734,14 @@ with a project-tagged custom agenda entry and optional CONTEXT-TAG (mu4e/agenda) (shell-command "git init") (let ((add-files ".gitignore 1-Plan/ 3-Notes/")) (when project-tag - (setq add-files (concat add-files " .projectile .dir-locals.el"))) + (setq add-files (concat add-files " .projectile .dir-locals.el .hermes-sessions"))) (shell-command (concat "git add -f " add-files))) (shell-command "git commit -m 'System: Initialize node repository'")) ;; existing repo: add new marker files if they exist (let ((add-files ".gitignore 1-Plan/ 3-Notes/")) (when project-tag - (setq add-files (concat add-files " .projectile .dir-locals.el"))) + (setq add-files (concat add-files " .projectile .dir-locals.el .hermes-sessions"))) (shell-command (concat "git add -f " add-files))))) ;; Pass the target file location back up to the caller @@ -2516,14 +2785,14 @@ Optional PROJECT-TAG and CONTEXT-TAG can be given to create .projectile and .dir dired-elements)))) (dirs (or marked-dirs (let* ((cursor-path (when (derived-mode-p 'dired-mode) - (dired-get-filename nil t))) + (dired-get-filename nil t))) (initial-path (cond ((and cursor-path (file-directory-p cursor-path)) cursor-path) (cursor-path (file-name-directory cursor-path)) (t default-directory)))) - (list (read-directory-name "Directory to convert: " nil initial-path t initial-path))))) + (list (read-directory-name "Directory to convert: " initial-path initial-path t))))) (tag (read-string "Project tag (blank to skip): ")) (ctx (read-string "Context tag (or blank): "))) (list dirs tag ctx))) @@ -2533,9 +2802,9 @@ Optional PROJECT-TAG and CONTEXT-TAG can be given to create .projectile and .dir (dolist (dir node-dirs) (let* ((node-dir (expand-file-name dir)) (org-file (gortium/ensure-node-structure node-dir - "Audit existing assets and reconcile tracking nodes" - (unless (string-empty-p project-tag) project-tag) - (unless (string-empty-p context-tag) context-tag)))) + "Audit existing assets and reconcile tracking nodes" + (unless (string-empty-p project-tag) project-tag) + (unless (string-empty-p context-tag) context-tag)))) (setq last-org-file org-file) (cl-incf count))) diff --git a/doom/.config/doom/hermes_logo_ascii.png b/doom/.config/doom/hermes_logo_ascii.png new file mode 100644 index 0000000000000000000000000000000000000000..05e80abc78080fe7b93c0bc811eeceb0f91b5daa GIT binary patch literal 5508 zcma)Ac{tQ-|DL47AraY=Ny1T$Wg=OlVQeXTmN60|d$LCn>clXTAdgtF6Vcy-yd_$vtQ5mv)uRne4=g{!8zE3*gzlbPw-N)S7EWM8Jg1JU}n%cdeXJHC{Mu@*-c}6QC?iJrcSx6>^-?! z9F3W*+qa#6nx(+cm(E@zTqV+Ci2es`J7N z8C@y~8(3FoquepI(K0W~`n?76(6DM>lKNVp>5DNL*RVaX`fRJj@RDv!OyZ`kr~8fF zuUYdtEx+^q?hfxC-Hlu0Y(Q?VyM@oL~d*zN044&hRUeJB_o1d9^o84cOy!u~_-iG!>URt3Om%WC4dJXZ>WWDJrg0>6|c3 z@jdV1t}D5*h*JrT$Dj&(^p4qMjPO^N{Pjw)7g@7-@vviZQ&h+=Oa8Yx8U% z!Dp>uX}G4oW&-yRmFDJw+Q4l4HRGz;t+9$wLUCvA!s7}8F`JAxk0Nn711A@s8CCbq zS^dN__&Lkjjyyv>7%BA%$X}c3qIJe{M0H(?&C~c5NuH@k(gsK*;>2vdNN0*Km7*df zjlnxt)=*HWB^=T+CYtmKAw)F`>O-O4xUrn@3f{>h5o{By*}eISskli-NywzQJN1{7HGCinD=tB6gQh&R!DopY z&A8;jY3owyw3K@6V}zVEz46JI?PzVmIM$$?7w&YuZMvfsP_Z_K&Pl`tO z275+EW{?mijL&vbfJiX@P06h~S;ZY{4>_|knrUcP&bQP0A z>EwXyjhy>sp6+F`(BMfR&-q;-$lJGV_ZO#^%;oU(JVa(H66k6j3~x}s@Inced1Q;i zcEH>NBTo$yo~8^6f5}gh#td~_)FI+5c{@m7(ShrliWPk{#^dc>o*A)9x7JtDX$8CJidy4RnS0e0GWX)z$l&#b=oBKQ&<-%H;!^9h z^*Vw}dWj91Iyfy-VCm$;Z)|ayhvkSpMC#ECOlK@7 zm9$NGaMz7GN_}&o;VR-ZF?+ohVz)rWGcOepYuFW`EUTnDP@!v228{jv?VXqQ7iR;5 zSh^Ov$JQHrZ77S~-X49=C=0cLZvqJ1btK9(WT4}8$I`RTd!Lw{;DgPdPoy6Z>W+SB zHWs!nUYH<`zR_Q%Tc?rip+2$=!iv}QD$!|GkFr>i+$mXLboYz$7!I`>yG|#K$Ac}n;!`il_YwhDLYw;a=zuM? z6|HEBS&p6Z6CUb%enKQ=9wfT+3Wc=v3?82BC1p~sjjE;K`23z#E4NXnMQU0l2-N9> zLMxrRKaRI_o^UH=v^?CTF0-n%g9IGlDbZ*VK4z}XFT6nBTe}!T{BLtzo8KWMLirAn zz`FbSDHp1u9-S4uzSsZI0hQiN;N%{!kg7Jg;C!*NRqCic#6P1?Nh5>H+BPKBwc?l< zwh+~{$}%Q8yWO0siem+4YWv1G8|ss&>bcjo_uL9-XHdiAxrCCPnXT=O18!DQAu ztP3`C*CT~^#b+x_;$e-|0=#om)&*{2JqBv!y7iK8BpU}fxqXFg79N+JQ&^c>H>tew zVY-D_Fzc|^IhmocsIRft-jvrCKB)u%EPcTcAwVKmS1c+S1y|njP-gVJ9y*~GueOh5 zl+QHO(B$`L0%sa5G<-Za}zzVM%pX%pnKL( z(iVp6a(W?qdihaA{Z2WyrhQOxzUSS3mnq9|_ajy49_{6%((A0HS&b4W&&TUS5jPz* z+B%13vbhWN@y(Iq6r#?_%ZpO0$ZHR}_foCgPCaSFlwD)hrPm;# ztG?B@u$)Y9xd6Xf{I9N>(C|Z9b{eG)ir2XHvc1G^Z1 z62gVefN=a+{C4lPPbC1@o%R3i={4q;oy4-ClMpkmg8~)wJUL8≪I(SxV4pT;5>|y!&2!Fyzy@9r5 ztXgHOE2r|mdYM4)-{t@*AbW@~y#K2?BMpNq$Dh#q_bkPX5i!wF_xVCu{+r*T-1^;g zaK@TisI70oqdWbWdXkMnXf(`EPP(h4pu7?Gd)$rBan%;}s7N7_@pY6cvNbmW`MBO} z+f*En?n&^P8e^EwH{ND^N^Mo+FPIgBQYFsW*%IuaTCpNdpyPl(ny64dVI4J}-TEBP zalZ`GZlp2R6Kel)B!qE%7zmt#2-=K#X3D;Qn|JzuGUm+UHA?`2?-hkK($-{_lsC+V zQ&+w4#!Wnu3u5$@zTL)&75*#Tb@<@Cf1dw|CF-P~g}^Ixj65Kxa$C@7mG;20)A~5D zoY;fz3rkY$x7k#gFb8Zr=lCVrQ+ywISPWnfURupPQ}=Oo@JO1P5@w8Z_Rbt^b4D09 zvHZXl`^11>mY>V||CZfBIu!`xRcSA2KWqs64eaQilpL`&3eumyNYJqMuo_>cjW;mV-rp;O|QA@b9d*>9Bk7s z9}*o?EflK#7S<#K}h9m3HG9=t>Il3{l5vi zSA~yESDs|}T?j>)M2S)@$L{+OK7EZ}n_ZGu8UBb&{^KRF9(x5|MU4-sCUt4<-qO6K zxa+W8&u_CaiY#I~THom}ZgA<^%r`=FV>p!2@J)mBc!wwTx2=1-E$s(e@rbS&WFp+hwAoPF zo3`LUOX3JU6n;RCYsUucvG$xd=0R}71bD?wU>Z4qOuu=sRlmns%_SsWEAD{mapwJV zwq>O`rRE!<_uJ#9E|0dBhp$(S_~|Qo1rr@3EBddmcZ7I-xb%oJV&;9Pjj?!PNUR4g zD&DS`%MNWN~QEnX=#ON-Z+AMs(rEa*a~Z$4t4>0E3uq?Rx2r zH0_`NCK+DzD8J9w&gd`FUZ@k*ie=-L7kg^>=-CalG68|61ZhMH9e`N{UK+dPGa z+MO1>kYy6QK^*k&%@y^tAn~o^VO_v0_fI@o3ui1=+ZVCfy$X+vQRY4K|gK+tdVoQ?noRyT=FBl#n&oKdg?zKa%zXG)?Kp*u6D`G2~J8m6U`h{ zjVavt1gMNn2vqIV&nwGJe`5(Lo}dM>*o;-L68Nzw`S>cx=YeJhAd%BUBNX>f@;Hfk zA#zKzPD|IVj!!(bW$X?=c{2DWjaxJ}0e6C95l~alMVw0W=`TV3ufm&>Y7Hh3WWk(B z8CVE{Y;Ak{0l9i`uKKchdWwx*kWgj-1NEGH8;mm)$HT$r&J>*^h#aQ*?WFCt#KkMu ze-?DYEv}9Cl`34lyL6EMjsKuB6)K@~@}}c&cMu96uUy9+683W-`7iuu`QoqY8!xuF z1)ai<7*U$P%)Kwz5B18N^gV6pT?V1RUaHuKCHR4kdzYOc^jXow?yv%$dS#f=|9Bd< zO`W{7W$H|f`{t!5zyv_bEBW~T6CwORyw+R`6CwqoGGY!vp{}`SwTDzfrfMug{ps6b6;03#@yh7)BGZ%(6h95`Zj|=-dXJuDpd8Gn z(zFGxD2&)WB;23wMxVR;jmNW_tT;@b-nHrcJn*6T6ttizDOAS7*4z_+n8KgCxz7LT z{A|ewVeZN&CamSkJMmn10I68j`>W}Px~nRZs7kAT09y;Pu4D-#QZub68El#@ZMC0x zoCn;w`am7sw9Dee&^Z5~F`v~MFr;eLl|KnCA_k!{s{NOBx8D`jW{%l) zBkq`5nSLOc(gd!GMB~ZHHl%p6qqU z7sfxmJ)O}PSlVXDuXZZaEs9%OH)g;wAAP2sP~Um|(W2l{mG+6L2}ijE{p9n>YKJ&{ zM&A>^HS1~8dJSOIU$()wlhB9Cp=`)F<**@LHdG4XH~o6@&JQGJddv3Tk3eJ1TRL3= z#{n8V{9E?ZOlfsx!N0@EgPsil7y66!|Bs3z0jNHZZ!HH4g0$uij$F;MVUUOmnLu?F Nq^E5JE7GzL{|7iPv*iE) literal 0 HcmV?d00001 diff --git a/doom/.config/doom/nil b/doom/.config/doom/nil deleted file mode 100644 index 951d4bc..0000000 --- a/doom/.config/doom/nil +++ /dev/null @@ -1,33 +0,0 @@ -(setq doom-theme 'doom-gruvbox) -;; Enable flashing mode-line on errors -(doom-themes-visual-bell-config) -;; Corrects (and improves) org-mode's native fontification. -(doom-themes-org-config) - -(after! org - ;; 1. Update existing faces (Org defines 1-8 by default) - (set-face-attribute 'org-document-title nil :height 2.0 :weight 'bold) - (set-face-attribute 'org-level-1 nil :weight 'semi-bold :height 1.45) - (set-face-attribute 'org-level-2 nil :weight 'semi-bold :height 1.35) - (set-face-attribute 'org-level-3 nil :weight 'semi-bold :height 1.25) - (set-face-attribute 'org-level-4 nil :weight 'semi-bold :height 1.15) - (set-face-attribute 'org-level-5 nil :weight 'normal :height 1.10) - (set-face-attribute 'org-level-6 nil :weight 'normal :height 1.05) - (set-face-attribute 'org-level-7 nil :weight 'normal :height 1.02) - (set-face-attribute 'org-level-8 nil :weight 'normal :height 1.00) - - ;; 2. Create missing faces (9-20) and inherit them directly from org-level-8 - (cl-loop for lvl from 9 to 20 - do (let ((face-name (intern (format "org-level-%d" lvl)))) - (custom-declare-face face-name - '((t :inherit org-level-8)) - (format "Face for org heading level %d." lvl) - :group 'org-faces))) - - ;; 3. Add the newly created faces to Org's level list - (setq org-level-faces - (append org-level-faces - (mapcar (lambda (lvl) (intern (format "org-level-%d" lvl))) - (number-sequence 9 20)))) - - (setq org-n-level-faces (length org-level-faces)))