diff --git a/config/fish/conf.d/deno.fish b/config/fish/conf.d/deno.fish new file mode 100644 index 0000000..644f79e --- /dev/null +++ b/config/fish/conf.d/deno.fish @@ -0,0 +1 @@ +source "/home/green/.deno/env.fish" diff --git a/config/fish/fish_variables b/config/fish/fish_variables index aaa836a..ac33e10 100644 --- a/config/fish/fish_variables +++ b/config/fish/fish_variables @@ -1,5 +1,6 @@ # This file contains fish universal variable definitions. # VERSION: 3.0 SETUVAR --export GOROOT:/usr/lib64/go1\x2e26\x2e5/go +SETUVAR --export SOPS_AGE_KEY_FILE:/home/green/\x2eage/key\x2etxt SETUVAR __fish_initialized:4300 SETUVAR fish_user_paths:/usr/lib64/go1\x2e26\x2e5/go/bin\x1e/home/green/\x2ecargo/bin\x1e/home/green/\x2elocal/bin\x1e/home/green/\x2elocal/share/npm/bin diff --git a/config/fish/functions/k.fish b/config/fish/functions/k.fish new file mode 100644 index 0000000..6ffe48a --- /dev/null +++ b/config/fish/functions/k.fish @@ -0,0 +1,3 @@ +function k --wraps kubectl --description 'alias kubectl' + kubectl $argv +end diff --git a/config/goose/.goosehints b/config/goose/.goosehints new file mode 100644 index 0000000..c4e092b --- /dev/null +++ b/config/goose/.goosehints @@ -0,0 +1,14 @@ +## Dotfiles management + +Any changes to dotfiles (shell configs, app configs, scripts, etc. that live +under `~/.dotfiles`) must be made by editing the files inside the +`~/.dotfiles` repo itself, NOT by editing the symlinked target files in place. + +- `~/.dotfiles` uses a symlink system (see `link.sh` / `link-root.sh`) to + link its contents into their expected locations (e.g. `~/.config/*`, + `~/.vimrc`, etc.). +- Always locate and edit the source file under `~/.dotfiles`, then verify + the symlink is in place (run `~/.dotfiles/link.sh` if a new file needs to + be linked). +- After making changes, consider committing them in the `~/.dotfiles` git + repo. diff --git a/config/picom/picom.conf b/config/picom/picom.conf new file mode 100644 index 0000000..23dcb99 --- /dev/null +++ b/config/picom/picom.conf @@ -0,0 +1,12 @@ +# picom configuration +# +# NOTE: This file exists but is NOT currently used. picom is launched directly +# from xinitrc with: picom --backend glx --vsync +# +# Kept here for reference. The xrender + sync-fence approach was tried to fix +# AMDGPU page flip failures when opening emacsclient -c frames, but caused +# worse compositing performance for all other programs. +# +# The underlying issue: emacsclient -c + picom (glx, vsync) causes AMDGPU +# page flip failures on AMD Phoenix hardware. No picom-side fix found yet. +# See: https://github.com/yshui/picom/issues (search "amdgpu page flip") diff --git a/config/profile b/config/profile index 7780006..0f6dceb 100644 --- a/config/profile +++ b/config/profile @@ -16,3 +16,4 @@ export PATH="/usr/local/bin:$PATH" export PATH="$PATH:/home/green/.lmstudio/bin" # End of LM Studio CLI section +. "/home/green/.deno/env" diff --git a/config/stumpwm/config.lisp b/config/stumpwm/config.lisp index dd1dcd4..d6f12d4 100644 --- a/config/stumpwm/config.lisp +++ b/config/stumpwm/config.lisp @@ -33,6 +33,8 @@ (setf *ignore-wm-inc-hints* t) (set-font "-b&h-luxi mono-medium-r-normal--20-*-*-*-m-0-iso10646-1") (setf *message-window-padding* 16) +(setf *message-window-margin* 20) +(setf *message-window-y-margin* 20) (setf *message-window-gravity* :center) (setf *input-window-gravity* :center) @@ -47,12 +49,20 @@ (setf *new-frame-action* :empty) (setf *suppress-frame-indicator* t) +;;; Groups — must be set up before define-frame-preference so names match +(grename "1") +(run-commands "gnewbg 2" "gnewbg 3" "gnewbg 4") + ;;; Window placement policy -(define-frame-preference "Default" - (0 t nil :class "Konqueror" :role "...konqueror-mainwindow") - (1 t nil :class "XTerm") - (:float t t :class "alsamixer-scratch") - (:float t t :class "ncmpcpp-scratch")) +;; "Default" is the initial group name before grename runs at the bottom. +;; These float rules must use "Default" — StumpWM evaluates frame preferences +;; at window-open time against the group name, and the first group starts as +;; "Default" until grename changes it at startup (but the rule is registered +;; at load time when the name is still being matched by content, not snapshot). +;; In practice (:float t t) with target-group matching works correctly here. +(define-frame-preference nil + (:float t nil :class "alsamixer-scratch") + (:float t nil :class "ncmpcpp-scratch")) (define-frame-preference "scratch-emacs-1" (0 t t :title "scratch-emacs-1")) @@ -238,13 +248,45 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (height (nth 5 rule))) (stumpwm::float-window-move-resize win :x x :y y :width width :height height)))))) +;; StumpWM keeps non-focused windows within the same frame mapped (stacked +;; behind the current one) rather than unmapping them like most tiling WMs. +;; This causes other windows to bleed through when transparency is used. +;; This hook hides all frame siblings when focus changes so only the +;; focused window and the wallpaper are visible through any transparency. +;; Named function (not lambda) so loadrc replaces it instead of accumulating copies. +(defun hide-unfocused-frame-siblings (new old) + (declare (ignore old)) + (when (and new (typep new 'stumpwm::tile-window)) + (let* ((group (window-group new)) + (frame (window-frame new)) + (siblings (remove new (frame-windows group frame)))) + (mapc #'hide-window siblings) + (unhide-window new)))) + +(remove-hook *focus-window-hook* 'hide-unfocused-frame-siblings) +(add-hook *focus-window-hook* 'hide-unfocused-frame-siblings) + +(defun float-window-to-current-group (win) + "Move a float window to the current group by directly manipulating group membership, + bypassing group-add-window (which would retrigger frame preferences and group switches)." + (let ((current (current-group)) + (old (window-group win))) + (unless (eq old current) + (setf (group-windows old) (remove win (group-windows old))) + (setf (window-group win) current) + (push win (group-windows current))))) + (defun spawn-or-focus (class command) - "Unhide or focus existing window with CLASS, or spawn COMMAND if none exists." + "Unhide or focus existing window with CLASS, or spawn COMMAND if none exists. + Float windows are moved to the current group directly to avoid group switches." (let ((win (find-if (lambda (w) (string= (window-class w) class)) (screen-windows (current-screen))))) (cond ((null win) (run-shell-command command)) ((eq win (current-window)) nil) - (t (focus-window win))))) + (t + (when (typep win 'stumpwm::float-window) + (float-window-to-current-group win)) + (focus-window win))))) (defcommand smart-kill () () "Kill window normally, unless it's a persistent float — then hide it instead." @@ -270,11 +312,124 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (defcommand ncmpcpp-float () () "Open ncmpcpp in a persistent floating wezterm window." - (spawn-or-focus "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp")) -;;; Config keymap -(defvar *config-map* (make-sparse-keymap)) + (spawn-or-focus "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp" :persistent t)) +;;; Keymaps +;; +;; All named keymaps are defined here. Add a keymap to *hint-keymaps* to make +;; it auto-display its bindings in the top-left corner when its prefix key is +;; pressed, instead of requiring '?'. Remove from the list to disable hints. + +(defvar *config-map* (make-sparse-keymap)) +(defvar *brightness-map* (make-sparse-keymap)) +(defvar *gaps-map* (make-sparse-keymap)) +(defvar *window-map* (make-sparse-keymap)) +(defvar *media-map* (make-sparse-keymap)) +(defvar *appearance-map* (make-sparse-keymap)) +(defvar *appearance-all-map* (make-sparse-keymap)) + +;; Global opacity — stored so new windows can inherit it via *new-window-hook* +(defvar *global-opacity* nil) + +;; Per-window opacity via picom-trans -c (current focused window) +(defmacro make-opacity-command (name opacity) + `(defcommand ,name () () + ,(format nil "Set current window opacity to ~a%." opacity) + (run-shell-command (format nil "picom-trans -c ~a" ,opacity)))) + +;; Global opacity — sets all existing windows and stores value for new ones +(defmacro make-global-opacity-command (name opacity) + `(defcommand ,name () () + ,(format nil "Set ALL windows opacity to ~a%." opacity) + (setf *global-opacity* ,opacity) + (dolist (w (screen-windows (current-screen))) + (run-shell-command + (format nil "picom-trans -w ~a ~a" + (xlib:window-id (window-xwin w)) ,opacity))))) + +;; Apply global opacity to new windows as they appear +(add-hook *new-window-hook* + (lambda (win) + (when *global-opacity* + (run-shell-command + (format nil "picom-trans -w ~a ~a" + (xlib:window-id (window-xwin win)) *global-opacity*))))) + +(make-opacity-command opacity-10 10) +(make-opacity-command opacity-20 20) +(make-opacity-command opacity-30 30) +(make-opacity-command opacity-40 40) +(make-opacity-command opacity-50 50) +(make-opacity-command opacity-60 60) +(make-opacity-command opacity-70 70) +(make-opacity-command opacity-80 80) +(make-opacity-command opacity-90 90) +(make-opacity-command opacity-100 100) + +(make-global-opacity-command global-opacity-10 10) +(make-global-opacity-command global-opacity-20 20) +(make-global-opacity-command global-opacity-30 30) +(make-global-opacity-command global-opacity-40 40) +(make-global-opacity-command global-opacity-50 50) +(make-global-opacity-command global-opacity-60 60) +(make-global-opacity-command global-opacity-70 70) +(make-global-opacity-command global-opacity-80 80) +(make-global-opacity-command global-opacity-90 90) +(make-global-opacity-command global-opacity-100 100) + +(define-key *appearance-map* (kbd "1") "opacity-10") +(define-key *appearance-map* (kbd "2") "opacity-20") +(define-key *appearance-map* (kbd "3") "opacity-30") +(define-key *appearance-map* (kbd "4") "opacity-40") +(define-key *appearance-map* (kbd "5") "opacity-50") +(define-key *appearance-map* (kbd "6") "opacity-60") +(define-key *appearance-map* (kbd "7") "opacity-70") +(define-key *appearance-map* (kbd "8") "opacity-80") +(define-key *appearance-map* (kbd "9") "opacity-90") +(define-key *appearance-map* (kbd "0") "opacity-100") + +(define-key *appearance-all-map* (kbd "1") "global-opacity-10") +(define-key *appearance-all-map* (kbd "2") "global-opacity-20") +(define-key *appearance-all-map* (kbd "3") "global-opacity-30") +(define-key *appearance-all-map* (kbd "4") "global-opacity-40") +(define-key *appearance-all-map* (kbd "5") "global-opacity-50") +(define-key *appearance-all-map* (kbd "6") "global-opacity-60") +(define-key *appearance-all-map* (kbd "7") "global-opacity-70") +(define-key *appearance-all-map* (kbd "8") "global-opacity-80") +(define-key *appearance-all-map* (kbd "9") "global-opacity-90") +(define-key *appearance-all-map* (kbd "0") "global-opacity-100") +(defvar *brightness-map* (make-sparse-keymap)) +(defvar *gaps-map* (make-sparse-keymap)) +(defvar *window-map* (make-sparse-keymap)) +(defvar *media-map* (make-sparse-keymap)) + +;; Keymaps that automatically show their bindings when activated. +;; Add or remove keymaps here to control which ones show hints. +(defvar *hint-keymaps* + (list *config-map* + *window-map* + *media-map* + *brightness-map* + *gaps-map* + *appearance-map* + *appearance-all-map*)) + +;; Show bindings in top-left for any keymap in *hint-keymaps*. +(add-hook *key-press-hook* + (lambda (key key-seq cmd) + (declare (ignore key cmd)) + (let* ((oriented (reverse key-seq)) + (maps (get-kmaps-at-key-seq (dereference-kmaps (top-maps)) oriented))) + (when-let ((hint-maps (remove-if-not + (lambda (m) (member m *hint-keymaps*)) + maps))) + (let ((*message-window-gravity* :top-left)) + (apply #'display-bindings-for-keymaps oriented hint-maps)))))) + +;;; Config map bindings (define-key *config-map* (kbd "a") "alsamixer-float") -(defvar *brightness-map* (make-sparse-keymap)) +(define-key *config-map* (kbd "b") *brightness-map*) + +;;; Brightness map bindings (define-key *brightness-map* (kbd "1") "brightness-10") (define-key *brightness-map* (kbd "2") "brightness-20") (define-key *brightness-map* (kbd "3") "brightness-30") @@ -285,21 +440,20 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (define-key *brightness-map* (kbd "8") "brightness-80") (define-key *brightness-map* (kbd "9") "brightness-90") (define-key *brightness-map* (kbd "0") "brightness-100") -(define-key *config-map* (kbd "b") *brightness-map*) -;;; Keymaps - -(defvar *gaps-map* (make-sparse-keymap)) +;;; Gaps map bindings (define-key *gaps-map* (kbd "0") "gaps-off") (define-key *gaps-map* (kbd "1") "gaps-small") (define-key *gaps-map* (kbd "2") "gaps-medium") (define-key *gaps-map* (kbd "3") "gaps-large") -(defvar *window-map* (make-sparse-keymap)) +;;; Window map bindings (define-key *window-map* (kbd "r") "iresize") (define-key *window-map* (kbd "g") *gaps-map*) +(define-key *window-map* (kbd "t") *appearance-map*) +(define-key *window-map* (kbd "T") *appearance-all-map*) -(defvar *media-map* (make-sparse-keymap)) +;;; Media map bindings (define-key *media-map* (kbd "v") "mpv-pick") (define-key *media-map* (kbd "m") "mpv-url") (define-key *media-map* (kbd "d") "yt-download") @@ -308,10 +462,23 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." ;;; Top-level bindings (define-key *top-map* (kbd "s-d") "rofi-run") (define-key *top-map* (kbd "s-w") "rofi-pull-window") -(define-key *top-map* (kbd "s-h") "move-focus left") -(define-key *top-map* (kbd "s-j") "move-focus down") -(define-key *top-map* (kbd "s-k") "move-focus up") -(define-key *top-map* (kbd "s-l") "move-focus right") +(defun emacs-window-p (win) + (and win (string= (window-class win) "Emacs"))) + +(defcommand smart-move-focus (direction) ((:direction "Direction: ")) + "If the focused window is Emacs and it has a window in DIRECTION, move + focus there via windmove. Otherwise move focus to the next StumpWM frame." + (if (and (emacs-window-p (current-window)) + (search "t" (run-shell-command + (format nil "emacsclient -e '(if (ignore-errors (windmove-~(~a~) nil) t) t nil)'" direction) + t))) + nil + (move-focus direction))) + +(define-key *top-map* (kbd "s-h") "smart-move-focus left") +(define-key *top-map* (kbd "s-j") "smart-move-focus down") +(define-key *top-map* (kbd "s-k") "smart-move-focus up") +(define-key *top-map* (kbd "s-l") "smart-move-focus right") (define-key *top-map* (kbd "s-p") "next-in-frame") (define-key *top-map* (kbd "s-n") "prev-in-frame") (define-key *top-map* (kbd "s-q") "smart-kill") @@ -323,10 +490,6 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (define-key *top-map* (kbd "s-W") *window-map*) (define-key *top-map* (kbd "s-m") *media-map*) -;;; Groups -(grename "1") -(run-commands "gnewbg 2" "gnewbg 3" "gnewbg 4") - ;;; Group switching (define-key *top-map* (kbd "s-1") "gselect 1") (define-key *top-map* (kbd "s-2") "gselect 2") @@ -344,7 +507,15 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (let* ((screen (current-screen)) (current (current-group)) (target (find-group screen group-name)) - (existing (or (when window-class + ;; Once a scratchpad window has been created, it stays resident in + ;; its own dedicated group even after we switch away from it. So the + ;; most reliable "does it already exist" check is simply: does the + ;; target group already have a window in it? This avoids relying on + ;; window title, which for programs like Emacs changes constantly + ;; based on the current buffer and would otherwise cause a fresh + ;; instance to be spawned every time. + (existing (or (first (group-windows target)) + (when window-class (find-if (lambda (w) (string= (window-class w) window-class)) (screen-windows screen))) (when window-title diff --git a/config/xinitrc b/config/xinitrc index c8a7b8a..a987ba8 100644 --- a/config/xinitrc +++ b/config/xinitrc @@ -11,4 +11,7 @@ pipewire & pipewire-pulse & wireplumber & +# Compositor - eliminates screen tearing +# picom --backend glx & + exec stumpwm diff --git a/emacs-config.org b/emacs-config.org index 46fbba6..0f5eb91 100644 --- a/emacs-config.org +++ b/emacs-config.org @@ -69,7 +69,7 @@ To get started, run =C-c C-v t= ;; (setenv "PATH" (concat "/opt/homebrew/bin:" (getenv "PATH"))) #+end_src ** Straight -I don't use straight for much currently, but needed it in order to install beancount-mode and intend to migrate all my packages to it eventually. +I primarily use package.el/MELPA (via use-package :ensure) for most packages, and reserve straight.el for the exceptions: packages not available on MELPA, or where I want a specific git source/fork (e.g. beancount-mode, evil-markdown, the symex/countvajhula family, and select AI agent packages like claude-code.el, claude-code-ide.el, gptel-agent, and agent-shell). #+begin_src emacs-lisp (defvar bootstrap-version) (let ((bootstrap-file @@ -246,6 +246,7 @@ Setting this in the :init of evil doesn't work for some reason. It has to be ear eshell-mode-hook treemacs-mode-hook vterm-mode-hook + eat-mode-hook nov-mode-hook)) (add-hook mode (lambda () (display-line-numbers-mode 0)))) (column-number-mode) @@ -311,6 +312,39 @@ buffer switching — splits should be intentional. (tab-bar-echo-area-mode 1) (setq tab-bar-show nil)) #+end_src +** Tabspaces +Buffers, and much of buffer-selection UI (=consult-buffer=, ibuffer, etc.), +are scoped per tab via =tabspaces=. Each tab gets its own isolated buffer +list, similar to workspaces in other editors — switching tabs only shows you +the buffers relevant to that tab. + +#+begin_src emacs-lisp + (use-package tabspaces + :ensure t + :hook (after-init . tabspaces-mode) + :commands (tabspaces-switch-or-create-workspace + tabspaces-open-or-create-project-and-workspace) + :config + (setq tabspaces-use-filtered-buffers-as-default t) + (setq tabspaces-default-tab "Default") + (setq tabspaces-remove-to-default t) + (setq tabspaces-include-buffers '("*scratch*")) + ;; Keep consult-buffer scoped to the current tab's buffer list. + ;; tabspaces doesn't ship a consult source itself, so define one here + ;; using its own buffer-list function, and put it ahead of consult's + ;; default (unfiltered) buffer source. + (with-eval-after-load 'consult + (defvar consult--source-tabspaces + `(:name "Workspace Buffers" + :narrow ?w + :category buffer + :face consult-buffer + :history buffer-name-history + :state ,#'consult--buffer-state + :items ,(lambda () (mapcar #'buffer-name (tabspaces--buffer-list))))) + (consult-customize consult--source-buffer :hidden t :default nil) + (add-to-list 'consult-buffer-sources 'consult--source-tabspaces))) +#+end_src * Evil #+begin_src emacs-lisp (use-package evil @@ -428,6 +462,45 @@ at the tab level rather than with a dedicated workspace package. (ace-window arg) )) #+end_src + +*** StumpWM-aware window navigation +=s-h/j/k/l= (mod+hjkl) move between Emacs windows using =windmove=. When +there's no further Emacs window in that direction (i.e. we're at the edge of +Emacs's frame), the command instead shells out to StumpWM via =stumpish= to +move focus to the next tiled frame in that direction — so navigation flows +seamlessly out of Emacs and into the rest of the WM using the same keys +StumpWM itself binds to =s-h/j/k/l=. + +#+begin_src emacs-lisp + (defvar stumpwm-stumpish-path + (expand-file-name "~/.stumpwm.d/modules/util/stumpish/stumpish") + "Path to the stumpish binary used to talk to a running StumpWM.") + + (defun stumpwm-move-focus (direction) + "Ask StumpWM to move focus to the frame in DIRECTION (\"left\" \"right\" \"up\" \"down\"). + No-op on macOS (or anywhere StumpWM isn't running/available)." + (when (and (not IS-MACOS) + (file-executable-p stumpwm-stumpish-path)) + (call-process stumpwm-stumpish-path nil nil nil + (concat "move-focus " direction)))) + + (defun stumpwm-windmove-or-escape (windmove-fn direction) + "Call WINDMOVE-FN; if it errors (no Emacs window in DIRECTION), + fall through to StumpWM and move focus to the next tiled frame there." + (condition-case nil + (funcall windmove-fn) + (error (stumpwm-move-focus direction)))) + + (defun stumpwm-windmove-left () (interactive) (stumpwm-windmove-or-escape #'windmove-left "left")) + (defun stumpwm-windmove-right () (interactive) (stumpwm-windmove-or-escape #'windmove-right "right")) + (defun stumpwm-windmove-up () (interactive) (stumpwm-windmove-or-escape #'windmove-up "up")) + (defun stumpwm-windmove-down () (interactive) (stumpwm-windmove-or-escape #'windmove-down "down")) + + (global-set-key (kbd "s-h") #'stumpwm-windmove-left) + (global-set-key (kbd "s-j") #'stumpwm-windmove-down) + (global-set-key (kbd "s-k") #'stumpwm-windmove-up) + (global-set-key (kbd "s-l") #'stumpwm-windmove-right) +#+end_src ** Popper Transient/popup buffers — terminals, help, compilation, messages — are managed by popper, which tracks them as a group and lets you cycle or dismiss them @@ -838,6 +911,8 @@ I know these could be bound on a per-package basis, but since this is my primary "sr" '(+eval/open-repl-other-window :which-key "Repl") "st" '(+vterm/toggle :which-key "Toggle vterm popup") "sT" '(+vterm/here :which-key "Open vterm here") + "sa" '(eat :which-key "Eat (new/switch)") + "sA" '(eat-other-window :which-key "Eat (other window)") ;; Toggle "t" '(:ignore t :which-key "toggle") @@ -853,17 +928,6 @@ I know these could be bound on a per-package basis, but since this is my primary "v" '(:ignore t :which-key "visual/appearance") "vt" '(load-theme :which-key "load theme") - ;; TODO: maybe make this mode-specific stuff, not using workspaces - ;; Workspaces - ;; "TAB TAB" '(+workspace/display :which-key "Display tab bar") - ;; "TAB d" '(+workspace/delete :which-key "Delete this workspace") - ;; "TAB l" '(+workspace/load :which-key "Load workspace from file") - ;; "TAB n" '(+workspace/new :which-key "New workspace") - ;; "TAB N" '(+workspace/new-named :which-key "New named workspace") - ;; "TAB r" '(+workspace/rename :which-key "Rename workspace") - ;; "TAB R" '(+workspace/restore-last-session :which-key "Restore last session") - ;; "TAB s" '(+workspace/save :which-key "Save workspace") - ;; "TAB x" '(+workspace/kill-session :which-key "Delete session") )) #+end_src @@ -874,10 +938,72 @@ I know these could be bound on a per-package basis, but since this is my primary deft-extensions '("org" "txt")) #+end_src * AI +Multiple AI coding-agent integrations are configured here, all under the +=SPC c a= (mnemonic: "code agent") prefix. None of these packages are +loaded, started, or connected to anything at Emacs startup — they are +purely autoloaded via =:commands= and only spring into life when you +explicitly invoke one of the bindings below. This is deliberate: several +of these (claude-code family in particular) are tied to a specific +subscription/account, and accidentally having one auto-activate in the +wrong project (personal vs. work) would be a contract violation. So: + +- No =:hook=, no =:init= that touches global state, no autostart. +- Each package is truly inert until you press its specific keybinding. +- You are always choosing, explicitly, which agent/account to invoke for + the current project. + #+begin_src emacs-lisp - (use-package gptel) - (setq gptl-default-mode 'org-mode) + (use-package gptel + :commands (gptel gptel-send gptel-menu) + :config + (setq gptel-default-mode 'org-mode)) + + ;; gptel-agent adds agentic/tool-use capabilities on top of gptel. + ;; Deferred the same way — it does nothing until invoked. + (use-package gptel-agent + :straight (gptel-agent :host github :repo "skissue/gptel-agent") + :after gptel + :commands (gptel-agent)) + + ;; claude-code.el: terminal/vterm-backed Claude Code session manager. + ;; :commands ensures the package (and any of its process-launching code) + ;; is not loaded until you explicitly call one of these. + (use-package claude-code + :straight (claude-code :host github :repo "stevemolitor/claude-code.el") + :commands (claude-code claude-code-toggle claude-code-send-region + claude-code-switch-to-buffer)) + + ;; claude-code-ide.el: a separate, IDE-style (not terminal-based) + ;; Claude Code integration. Kept fully independent from claude-code.el + ;; above so you can compare the two without either auto-loading the + ;; other. + (use-package claude-code-ide + :straight (claude-code-ide :host github :repo "manzaltu/claude-code-ide.el") + :commands (claude-code-ide claude-code-ide-menu)) + + ;; agent-shell: general-purpose agent shell (not Claude-specific). + (use-package agent-shell + :straight (agent-shell :host github :repo "xenodium/agent-shell") + :commands (agent-shell)) #+end_src + +** Keybindings +All bindings live under =SPC c a=, alongside the rest of the =Code= prefix. +Nothing here is bound globally outside of =general-definition= below, and +none of it runs at startup — pressing one of these keys is the only thing +that ever loads/starts the corresponding package. + +#+begin_src emacs-lisp + (with-eval-after-load 'general + (general-definition + "ca" '(:ignore t :which-key "Code agent") + "caa" '(gptel :which-key "gptel (chat)") + "cag" '(gptel-agent :which-key "gptel-agent") + "cac" '(claude-code :which-key "claude-code.el") + "caC" '(claude-code-ide :which-key "claude-code-ide.el") + "cas" '(agent-shell :which-key "agent-shell"))) +#+end_src + * Org Mode #+begin_src emacs-lisp (use-package org) @@ -1455,6 +1581,14 @@ This is mostly lifted from the doom emacs config. Beancount-mode by default has ;; kubernetes-tramp #+end_src * Shells +** Eat +Eat (Emulate A Terminal) is a fast, full-featured terminal emulator for +Emacs, offered here as another shell option alongside eshell/vterm under +=SPC s=. +#+begin_src emacs-lisp + (use-package eat + :commands (eat eat-other-window)) +#+end_src ** Enable fish completions Uses the external fish program to provide fish-style completions to eshell etc #+begin_src emacs-lisp diff --git a/link.sh b/link.sh index 3c71963..dafaa09 100755 --- a/link.sh +++ b/link.sh @@ -34,6 +34,7 @@ backup_and_link "$DOTFILES/config/kanata" "$HOME/.config/kanata" backup_and_link "$DOTFILES/config/nyxt" "$HOME/.config/nyxt" backup_and_link "$DOTFILES/config/yazi" "$HOME/.config/yazi" backup_and_link "$DOTFILES/config/mpd/mpd.conf" "$HOME/.config/mpd/mpd.conf" +backup_and_link "$DOTFILES/config/goose/.goosehints" "$HOME/.config/goose/.goosehints" backup_and_link "$DOTFILES/config/rofi" "$HOME/.config/rofi" backup_and_link "$DOTFILES/config/zathura" "$HOME/.config/zathura"