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-21 12:23:07 +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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 "
:models ' ( google/gemini-2.5-flash
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 "
:model 'google/gemini-2.5-flash
: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
;; 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
:desc " Proofread text " " p " #' my/gptel-proofread-region ) )
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))))
;; (map! :leader
;; (:prefix ("C" . "claude-code")
;; :desc "Claudemacs menu" "E" #'claudemacs-transient-menu)))