2025-10-19 21:54:29 +08:00
;;; ai.el -*- lexical-binding: t; -*-
2025-10-19 22:16:22 +08:00
;;; ============================================================================
;;; gptel Configuration
;;; ============================================================================
;; Core gptel setup
( use-package! gptel
2025-11-22 13:52:03 +08:00
:defer t
2025-10-19 22:16:22 +08:00
:config
;; Default backend: OpenRouter
2026-03-23 15:34:41 +08:00
( setq gptel-model 'google/gemini-3-flash-preview
2025-10-19 22:16:22 +08:00
gptel-backend
( gptel-make-openai "OpenRouter"
:host "openrouter.ai"
:endpoint "/api/v1/chat/completions"
:stream t
:key "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"
2026-03-23 15:34:41 +08:00
:models ' ( google/gemini-3-flash-preview
2025-11-22 20:13:14 +08:00
x-ai/grok-4.1-fast
x-ai/grok-code-fast-1
google/gemini-3-pro-preview
2025-10-19 22:16:22 +08:00
anthropic/claude-sonnet-4.5 )))
;; Use org-mode by default for better note-taking
( setq gptel-default-mode 'org-mode )
;; Move cursor to end of response automatically
( add-hook 'gptel-post-response-functions 'gptel-end-of-response )
2025-11-07 01:26:43 +08:00
;; Keybindings - using SPC A for AI (uppercase to avoid conflicts)
2025-10-19 22:16:22 +08:00
( map! :leader
2025-11-07 01:26:43 +08:00
( :prefix ( "A" . "AI/gptel" )
2025-10-19 22:16:22 +08:00
:desc "gptel chat" "g" #' gptel
:desc "gptel send" "s" #' gptel-send
:desc "gptel menu" "m" #' gptel-menu
:desc "gptel rewrite" "r" #' gptel-rewrite
:desc "Add context" "c" #' gptel-add
:desc "Add file to context" "f" #' gptel-add-file ))
;; Additional keybindings in gptel buffer
( map! :map gptel-mode-map
"C-c RET" #' gptel-send
"C-c C-k" #' gptel-abort
"C-c C-c" #' gptel-menu ))
;; gptel presets for different tasks
( after! gptel
;; Coding assistant preset
( gptel-make-preset 'coder
2026-03-21 12:23:07 +08:00
:description "Expert coding assistant"
:backend "OpenRouter"
:model 'anthropic/claude-sonnet-4.5
:system "You are an expert coding assistant. Provide high-quality code solutions, refactorings, and explanations. Be concise but thorough. Include error handling and follow best practices." )
2025-10-19 22:16:22 +08:00
;; Code explainer preset
( gptel-make-preset 'explain
2026-03-21 12:23:07 +08:00
:description "Explain code to beginners"
:system "Explain this code to a novice programmer. Use simple language and break down complex concepts." )
2025-10-19 22:16:22 +08:00
;; Refactoring preset
( gptel-make-preset 'refactor
2026-03-21 12:23:07 +08:00
:description "Code refactoring expert"
:model 'anthropic/claude-sonnet-4.5
:system "You are a code refactoring expert. Improve code quality, readability, and performance while maintaining functionality. Explain your changes." )
2025-10-19 22:16:22 +08:00
;; Quick answers preset
( gptel-make-preset 'quick
2026-03-21 12:23:07 +08:00
:description "Quick, concise answers"
2026-03-23 15:34:41 +08:00
:model 'google/gemini-3-flash-preview
2026-03-21 12:23:07 +08:00
:system "Provide quick, concise answers. Be direct and to the point." )
2025-10-19 22:16:22 +08:00
;; Documentation writer preset
( gptel-make-preset 'docs
2026-03-21 12:23:07 +08:00
:description "Documentation writer"
:system "Write clear, comprehensive documentation. Include examples and explain edge cases." ))
2025-10-19 22:16:22 +08:00
;;; ============================================================================
;;; gptel-quick - Quick lookups and explanations
;;; ============================================================================
( use-package! gptel-quick
:after gptel
2025-11-22 13:52:03 +08:00
:defer t
2025-10-19 22:16:22 +08:00
:config
;; Include context from gptel-add if available
( setq gptel-quick-use-context t )
( map! :leader
2025-11-07 01:26:43 +08:00
( :prefix "A"
2025-10-19 22:16:22 +08:00
:desc "Quick lookup" "q" #' gptel-quick )))
;;; ============================================================================
;;; gptel-extensions - Extra utility functions
;;; ============================================================================
( use-package! gptel-extensions
:after gptel
2025-11-22 13:52:03 +08:00
:defer t
2025-10-19 22:16:22 +08:00
:config
( map! :leader
2025-11-07 01:26:43 +08:00
( :prefix "A"
2025-10-19 22:16:22 +08:00
:desc "Load buffer into session" "l" #' gptel-extensions-load-buffer
:desc "Send whole buffer" "b" #' gptel-extensions-send-whole-buffer
:desc "Refactor region" "R" #' gptel-extensions-refactor )))
;;; ============================================================================
;;; gptel-autocomplete - Inline code completion
;;; ============================================================================
( use-package! gptel-autocomplete
:after gptel
2025-11-22 13:52:03 +08:00
:defer t
2025-11-07 01:26:43 +08:00
:commands ( gptel-complete gptel-accept-completion )
2025-10-19 22:16:22 +08:00
:config
2025-11-07 01:26:43 +08:00
;; Configure context size
( setq gptel-autocomplete-before-context-lines 100
gptel-autocomplete-after-context-lines 20
gptel-autocomplete-temperature 0.1 )
2025-10-19 22:16:22 +08:00
2025-11-07 01:26:43 +08:00
;; Keybindings for autocomplete
( map! :leader
( :prefix "A"
:desc "Complete at point" "C" #' gptel-complete
:desc "Accept completion" "a" #' gptel-accept-completion ))
2025-10-19 22:16:22 +08:00
2025-11-07 01:26:43 +08:00
;; Also add convenient keybindings in prog-mode
( map! :map prog-mode-map
"M-TAB" #' gptel-complete
"C-c TAB" #' gptel-complete
"C-c C-a" #' gptel-accept-completion ))
2025-10-19 22:16:22 +08:00
;;; ============================================================================
;;; Optional: Add Ollama for local models (commented out by default)
;;; ============================================================================
;; Uncomment to add Ollama support for local models
;; (after! gptel
;; (gptel-make-ollama "Ollama"
;; :host "localhost:11434"
;; :stream t
;; :models '(qwen2.5-coder:latest
;; mistral:latest
;; llama3.2:latest)))
;; To make Ollama default, uncomment:
;; (setq gptel-model 'qwen2.5-coder:latest
;; gptel-backend (gptel-make-ollama "Ollama"
;; :host "localhost:11434"
;; :stream t
;; :models '(qwen2.5-coder:latest
;; mistral:latest)))
;;; ============================================================================
;;; Helper functions
;;; ============================================================================
( defun my/gptel-quick-explain-region ()
"Quickly explain selected code region."
( interactive )
( when ( use-region-p )
( let (( code ( buffer-substring-no-properties ( region-beginning ) ( region-end ))))
( gptel-request code
2026-03-21 12:23:07 +08:00
:system "Explain this code concisely in 2-3 sentences."
:callback ( lambda ( response info )
( message "Explanation: %s" response ))))))
2025-10-19 22:16:22 +08:00
( defun my/gptel-proofread-region ()
"Proofread and improve selected text."
( interactive )
( when ( use-region-p )
( gptel-rewrite-region ( region-beginning ) ( region-end )
2025-11-22 20:13:14 +08:00
"Fix spelling, grammar, and improve clarity. Keep the same tone and style." )))
2025-10-19 22:16:22 +08:00
2026-03-23 15:36:58 +08:00
( defun my/copy-region-as-path-tag ()
"Copy the current region as a path tag: @path/to/file.ext:start-end"
( interactive )
( if ( use-region-p )
( let* (( filename ( file-relative-name ( buffer-file-name ) ( doom-project-root )))
( start-line ( line-number-at-pos ( region-beginning )))
;; If region ends at beginning of a line, we want the previous line
( end-pos ( 1- ( region-end )))
( end-line ( line-number-at-pos ( max ( region-beginning ) end-pos )))
( tag ( format "@%s:%d-%d" filename start-line end-line )))
( kill-new tag )
( message "Copied: %s" tag ))
( let* (( filename ( file-relative-name ( buffer-file-name ) ( doom-project-root )))
( line ( line-number-at-pos ))
( tag ( format "@%s:%d" filename line )))
( kill-new tag )
( message "Copied: %s" tag ))))
2025-10-19 22:16:22 +08:00
;; Add keybindings for helper functions
( map! :leader
2025-11-07 01:26:43 +08:00
( :prefix "A"
2025-10-19 22:16:22 +08:00
:desc "Quick explain" "e" #' my/gptel-quick-explain-region
2026-03-23 15:36:58 +08:00
:desc "Proofread text" "p" #' my/gptel-proofread-region
:desc "Copy path tag" "t" #' my/copy-region-as-path-tag ))
2025-10-19 21:57:36 +08:00
;;; Claude Code Configuration
( use-package! claude-code
2025-11-22 13:52:03 +08:00
:defer t
2025-10-19 21:57:36 +08:00
:config
2026-03-21 12:23:07 +08:00
;; Unset CLAUDECODE to allow running Claude Code inside Emacs sub-processes
( setenv "CLAUDECODE" nil )
2026-03-05 21:16:11 +08:00
( setq claude-code-program "ccr"
claude-code-program-switches ' ( "code" "--dangerously-skip-permissions" ))
2026-03-06 02:19:39 +08:00
;; Use Eat backend
2026-03-15 13:52:36 +08:00
( setq claude-code-terminal-backend 'eat
;; Emacs-style editing in Claude buffer: RET inserts newline, S-RET sends
claude-code-newline-keybinding-style 'shift-return-to-send )
2025-10-19 21:57:36 +08:00
;; Enable global mode
( claude-code-mode 1 )
2026-03-15 13:52:36 +08:00
;; Emacs-style prefix key (works without Doom/Evil leader keys)
( global-set-key ( kbd "C-c c" ) claude-code-command-map )
;; Set Doom leader keybindings
2025-10-19 21:57:36 +08:00
( map! :leader
( :prefix ( "C" . "claude-code" )
:desc "Start Claude" "c" #' claude-code
:desc "Start in directory" "d" #' claude-code-start-in-directory
:desc "Continue conversation" "C" #' claude-code-continue
:desc "Resume session" "R" #' claude-code-resume
:desc "New instance" "i" #' claude-code-new-instance
:desc "Kill Claude" "k" #' claude-code-kill
:desc "Kill all instances" "K" #' claude-code-kill-all
:desc "Send command" "s" #' claude-code-send-command
:desc "Send command with context" "x" #' claude-code-send-command-with-context
:desc "Send region/buffer" "r" #' claude-code-send-region
:desc "Send buffer file" "o" #' claude-code-send-buffer-file
:desc "Fix error at point" "e" #' claude-code-fix-error-at-point
:desc "Toggle window" "t" #' claude-code-toggle
:desc "Switch to buffer" "b" #' claude-code-switch-to-buffer
:desc "Select buffer" "B" #' claude-code-select-buffer
:desc "Toggle read-only" "z" #' claude-code-toggle-read-only-mode
:desc "Cycle mode" "M" #' claude-code-cycle-mode
:desc "Transient menu" "m" #' claude-code-transient
:desc "Slash commands" "/" #' claude-code-slash-commands
:desc "Fork conversation" "f" #' claude-code-fork
:desc "Send return" "y" #' claude-code-send-return
:desc "Send escape" "n" #' claude-code-send-escape
:desc "Send 1" "1" #' claude-code-send-1
:desc "Send 2" "2" #' claude-code-send-2
:desc "Send 3" "3" #' claude-code-send-3 ))
;; Optional: Configure window display (side window on right)
( add-to-list 'display-buffer-alist
' ( "^\\*claude"
( display-buffer-in-side-window )
( side . right )
( window-width . 90 )))
;; Optional: Enable auto-revert for files modified by Claude
( global-auto-revert-mode 1 )
;; Optional: Increase vterm scrollback for long conversations
( add-hook 'claude-code-start-hook
( lambda ()
( when ( eq claude-code-terminal-backend 'vterm )
( setq-local vterm-max-scrollback 100000 )))))
2026-03-21 12:27:00 +08:00
;;; Gemini CLI Configuration
( use-package! gemini-cli
:defer t
:config
;; Set the Gemini executable
( setq gemini-cli-program "gemini"
gemini-cli-program-switches nil )
;; Use Eat backend
( setq gemini-cli-terminal-backend 'eat
;; Emacs-style editing in Gemini buffer: RET inserts newline, S-RET sends
gemini-cli-newline-keybinding-style 'shift-return-to-send )
;; Enable global mode
( gemini-cli-mode 1 )
;; Emacs-style prefix key
( global-set-key ( kbd "C-c g" ) gemini-cli-command-map )
;; Set Doom leader keybindings under SPC G
( map! :leader
( :prefix ( "G" . "gemini-cli" )
:desc "Start Gemini" "c" #' gemini-cli
:desc "Start in directory" "d" #' gemini-cli-start-in-directory
:desc "Continue conversation" "C" #' gemini-cli-continue
:desc "Resume session" "R" #' gemini-cli-resume
:desc "New instance" "i" #' gemini-cli-new-instance
:desc "Kill Gemini" "k" #' gemini-cli-kill
:desc "Kill all instances" "K" #' gemini-cli-kill-all
:desc "Send command" "s" #' gemini-cli-send-command
:desc "Send command with context" "x" #' gemini-cli-send-command-with-context
:desc "Send region/buffer" "r" #' gemini-cli-send-region
:desc "Send buffer file" "o" #' gemini-cli-send-buffer-file
:desc "Fix error at point" "e" #' gemini-cli-fix-error-at-point
:desc "Toggle window" "t" #' gemini-cli-toggle
:desc "Switch to buffer" "b" #' gemini-cli-switch-to-buffer
:desc "Select buffer" "B" #' gemini-cli-select-buffer
:desc "Toggle read-only" "z" #' gemini-cli-toggle-read-only-mode
:desc "Cycle mode" "M" #' gemini-cli-cycle-mode
:desc "Transient menu" "m" #' gemini-cli-transient
:desc "Slash commands" "/" #' gemini-cli-slash-commands
:desc "Fork conversation" "f" #' gemini-cli-fork
:desc "Send return" "y" #' gemini-cli-send-return
:desc "Send escape" "n" #' gemini-cli-send-escape
:desc "Send 1" "1" #' gemini-cli-send-1
:desc "Send 2" "2" #' gemini-cli-send-2
:desc "Send 3" "3" #' gemini-cli-send-3 ))
;; Optional: Configure window display (side window on right)
( add-to-list 'display-buffer-alist
' ( "^\\*gemini"
( display-buffer-in-side-window )
( side . right )
( window-width . 90 )))
;; Optional: Enable auto-revert for files modified by Gemini
;; (global-auto-revert-mode 1)
;; Optional: Increase vterm scrollback for long conversations
( add-hook 'gemini-cli-start-hook
( lambda ()
( when ( eq gemini-cli-terminal-backend 'vterm )
( setq-local vterm-max-scrollback 100000 )))))
2026-03-05 21:16:11 +08:00
2026-03-06 02:19:39 +08:00
;; (use-package! claudemacs
;; :defer t
;; :commands (claudemacs claudemacs-transient-menu)
;; :init
;; (setq claudemacs-program "ccr"
;; claudemacs-program-switches '("code" "--dangerously-skip-permissions")
;; claudemacs-default-tool 'claude
;; claudemacs-prefer-projectile-root t)
;; :config
;; (map! :map prog-mode-map
;; "C-c C-e" #'claudemacs-transient-menu)
;; (map! :map emacs-lisp-mode-map
;; "C-c C-e" #'claudemacs-transient-menu)
;; (map! :map text-mode-map
;; "C-c C-e" #'claudemacs-transient-menu)
;; (after! python
;; (map! :map python-base-mode-map
;; "C-c C-e" #'claudemacs-transient-menu))
;; (after! eat
;; (setq eat-term-scrollback-size 400000)
;; (add-hook 'eat-mode-hook
;; (lambda ()
;; (setq-local show-trailing-whitespace nil))))
;; :desc "Claudemacs menu" "E" #'claudemacs-transient-menu)))