I’ve been using Claude for almost two years now, and about six months ago I switched completely to claude-code for most coding activities. I was initially using gptel and ellama, but at some point I came across claude-code.el, which allowed me to smoothly integrate Claude Code into Emacs. In this post I’ll share my customizations — yes, most of them were generated by Claude.

Setup #

Dependencies: inheritenv and eat as terminal backend.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(use-package inheritenv
  :straight (:type git :host github :repo "purcell/inheritenv"))

(use-package eat
  :defer t
  :commands (eat eat-project eat-other-window)
  :straight (:type git
                   :host codeberg
                   :repo "akib/emacs-eat"
                   :files ("*.el" ("term" "term/*.el") "*.texi"
                           "*.ti" ("terminfo/e" "terminfo/e/*")
                           ("terminfo/65" "terminfo/65/*")
                           ("integration" "integration/*")
                           (:exclude ".dir-locals.el" "*-tests.el"))))

(use-package claude-code
  :straight (:type git :host github :repo "manzaltu/claude-code.el"
                   :branch "main" :depth 1
                   :files ("*.el" (:exclude "images/*")))
  :bind-keymap
  ("C-x c" . claude-code-command-map)
  :bind
  (:repeat-map my-claude-code-map ("M" . claude-code-cycle-mode))
  :config
  (setq claude-code-terminal-backend 'eat)
  (define-key claude-code-command-map (kbd "p") #'claude-code-send-command)
  (define-key claude-code-command-map (kbd "r") #'my/claude-code-send-region)
  (define-key claude-code-command-map (kbd "R") #'my/claude-code-send-region-with-prompt)
  (define-key claude-code-command-map (kbd "I") #'my/claude-code-send-region-select-instance)
  (define-key claude-code-command-map (kbd "X") #'my/claude-code-send-command-with-context-select-instance))
Code Snippet 1: Basic setup
Table 1: Key bindings overview
Key binding Command Description
C-x c (prefix) claude-code-command-map Entry point for all Claude Code commands
C-x c p claude-code-send-command Send a plain prompt to Claude
C-x c r my/claude-code-send-region Send selected region (fenced) to the active Claude buffer, no prompt
C-x c R my/claude-code-send-region-with-prompt Send selected region (fenced) + prompt to the active Claude buffer
C-x c I my/claude-code-send-region-select-instance Send selected region + prompt, choose which Claude buffer to send to
C-x c X my/claude-code-send-command-with-context-select-instance Send a command with file/region context to a chosen buffer
M (repeat) claude-code-cycle-mode Cycle through auto-accept / plan / confirm modes (repeat map)

Sending a region #

Select a region and send it to Claude, optionally with a prompt. my/claude-code-send-region sends the raw region fenced in backticks. my/claude-code-send-region-with-prompt additionally reads a prompt from the minibuffer and appends it after the fence.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun my/claude-code--build-region-cmd (beg end)
  "Build the command string for sending region BEG to END with a prompt."
  (let* ((region-text (buffer-substring-no-properties beg end))
         (prompt (read-string "Prompt for Claude: " nil 'claude-code-command-history)))
    (format "```\n%s\n```\n\n%s"
            region-text
            prompt)))

(defun my/claude-code-send-region (beg end)
  "Send region to Claude without a prompt."
  (interactive "r")
  (unless (use-region-p)
    (user-error "No region selected"))
  (let ((region-text (buffer-substring-no-properties beg end)))
    (claude-code--do-send-command (format "```\n%s\n```" region-text))))

(defun my/claude-code-send-region-with-prompt (beg end)
  "Send region to Claude wrapped in delimiters, then ask for a prompt."
  (interactive "r")
  (unless (use-region-p)
    (user-error "No region selected"))
  (claude-code--do-send-command (my/claude-code--build-region-cmd beg end)))

Sending file context #

my/claude-code-send-command-with-context-select-instance attaches the current file reference (or line range if a region is active) to a prompt, then routes it to a chosen Claude buffer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defun my/claude-code-send-command-with-context-select-instance ()
  "Like `claude-code-send-command-with-context' but prompt for Claude instance."
  (interactive)
  (let* ((cmd (read-string "Claude command: " nil 'claude-code-command-history))
         (file-ref (if (use-region-p)
                       (claude-code--format-file-reference
                        nil
                        (line-number-at-pos (region-beginning) t)
                        (line-number-at-pos (region-end) t))
                     (claude-code--format-file-reference)))
         (cmd-with-context (if file-ref (format "%s\n%s" cmd file-ref) cmd)))
    (my/claude-code--send-to-instance cmd-with-context)))

Selecting a different instance #

When running multiple Claude sessions in parallel (one per project), these functions let you pick which buffer to target rather than always defaulting to the current one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(defun my/claude-code--send-to-instance (cmd)
  "Prompt for a Claude instance and send CMD to it."
  (let* ((all-buffers (claude-code--find-all-claude-buffers))
         (target (claude-code--select-buffer-from-choices
                  "Send to Claude instance: " all-buffers)))
    (if target
        (with-current-buffer target
          (claude-code--term-send-string claude-code-terminal-backend cmd)
          (sit-for 0.1)
          (claude-code--term-send-string claude-code-terminal-backend (kbd "RET"))
          (display-buffer target))
      (user-error "No Claude instance selected"))))

(defun my/claude-code-send-region-select-instance (beg end)
  "Like `my/claude-code-send-region-with-prompt' but prompt for Claude instance."
  (interactive "r")
  (unless (use-region-p)
    (user-error "No region selected"))
  (my/claude-code--send-to-instance (my/claude-code--build-region-cmd beg end)))

Switching between profiles #

I have two Claude accounts: personal (claude.ai) and a corporate Claude proxy at work. The work account uses a separate settings.work.json with a different ANTHROPIC_BASE_URL and model names.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(defvar claude-code-current-profile "personal"
  "Current Claude Code profile (personal or work).")

(defun claude-code-use-work-account ()
  "Switch Claude Code to use work account settings."
  (interactive)
  (setq claude-code-program-switches
        (list "--settings" (expand-file-name "~/.claude/settings.work.json")))
  (setq claude-code-current-profile "work")
  (message "Claude Code: Switched to work account. Start a new Claude session."))

(defun claude-code-use-personal-account ()
  "Switch Claude Code to use personal account settings (default)."
  (interactive)
  (setq claude-code-program-switches nil)
  (setq claude-code-current-profile "personal")
  (message "Claude Code: Switched to personal account. Start a new Claude session."))

(defun claude-code-toggle-account ()
  "Toggle between personal and work Claude Code accounts."
  (interactive)
  (if (string= claude-code-current-profile "personal")
      (claude-code-use-work-account)
    (claude-code-use-personal-account)))

(defun claude-code-show-current-profile ()
  "Display the current Claude Code profile."
  (interactive)
  (let ((account claude-code-current-profile)
        (switches (if claude-code-program-switches
                      (format "Settings: %s" claude-code-program-switches)
                    "Default settings (personal)")))
    (message "Claude Code Profile: %s | %s" account switches)))

claude-code-toggle-account flips the profile; starting a new Claude session picks up the new settings.