Compare commits

..

No commits in common. "1fd66682e5f103db53c041d9ae3addf8ae9d5d0c" and "3078e8d39d2c7faa3e888ff5d26a4ee3c7a56051" have entirely different histories.

3 changed files with 279 additions and 508 deletions

View file

@ -57,28 +57,8 @@
'(org-log-done t) '(org-log-done t)
'(org-odd-levels-only t) '(org-odd-levels-only t)
'(org-todo-keywords '((sequence "TODO" "|" "DONE" "ABANDONED" "DEFERRED"))) '(org-todo-keywords '((sequence "TODO" "|" "DONE" "ABANDONED" "DEFERRED")))
'(package-check-signature nil)
'(package-selected-packages '(package-selected-packages
'(adaptive-wrap add-node-modules-path ag auto-complete auto-complete-nxml '(gptel fish-mode editorconfig jsonnet-mode scala-ts-mode adaptive-wrap add-node-modules-path ag auto-complete auto-complete-nxml bazel blacken cider clang-format clojure-mode color-theme-monokai color-theme-sanityinc-solarized color-theme-sanityinc-tomorrow company company-jedi company-lsp compat cquery dash-functional deadgrep dockerfile-mode doom-themes earthfile-mode eglot eglot-java elm-mode esup exec-path-from-shell filladapt flycheck flycheck-elm flycheck-rust flymake flyspell fsharp-mode geiser gnu-elpa-keyring-update go-autocomplete go-mode graphviz-dot-mode hack-mode haxe-mode howm ink-mode js2-mode js2-refactor json-mode lsp-hack lsp-pyright lsp-ui lua-mode magit markdown-mode merlin mocha modus-themes monky monokai-theme multi-term mustache-mode nyan-mode paredit popup prettier-js projectile protobuf-mode python-mode rjsx-mode ruby-mode rust-mode sql-indent swift-mode switch-window terraform-mode thrift tide tree-sitter tss tuareg typescript-mode use-package vterm web-mode wgrep xref-js2 xterm-color yaml-mode zig-mode))
bazel blacken cider clang-format clipetty clojure-mode
color-theme-monokai color-theme-sanityinc-solarized
color-theme-sanityinc-tomorrow company company-jedi
company-lsp compat cquery dash-functional deadgrep
dockerfile-mode doom-themes earthfile-mode editorconfig
eglot eglot-java elm-mode esup exec-path-from-shell
filladapt fish-mode flycheck flycheck-elm flycheck-rust
flymake flyspell fsharp-mode geiser
gnu-elpa-keyring-update go-autocomplete go-mode gptel
graphviz-dot-mode hack-mode haxe-mode howm ink-mode
js2-mode js2-refactor json-mode jsonnet-mode lsp-hack
lsp-pyright lsp-ui lua-mode magit markdown-mode merlin
mocha modus-themes monky monokai-theme multi-term
mustache-mode nyan-mode paredit popup prettier-js
projectile protobuf-mode python-mode request rjsx-mode
ruby-mode rust-mode scala-mode scala-ts-mode sql-indent
swift-mode switch-window terraform-mode thrift tide
tree-sitter tss tuareg typescript-mode use-package vterm
web-mode wgrep xref-js2 xterm-color yaml-mode zig-mode))
'(reb-re-syntax 'string) '(reb-re-syntax 'string)
'(rmail-mail-new-frame t) '(rmail-mail-new-frame t)
'(safe-local-variable-values '(safe-local-variable-values
@ -93,8 +73,8 @@
(whitespace-mode 0) (whitespace-mode 0)
(whitespace-mode 1)) (whitespace-mode 1))
(whitespace-line-column . 80) (whitespace-line-column . 80)
(whitespace-style face trailing lines-tail) (require-final-newline . t))) (whitespace-style face trailing lines-tail)
'(scala-indent:use-javadoc-style t) (require-final-newline . t)))
'(scroll-conservatively 1) '(scroll-conservatively 1)
'(scroll-step 1) '(scroll-step 1)
'(sd-user-email "johndoty@microsoft.com") '(sd-user-email "johndoty@microsoft.com")
@ -106,7 +86,6 @@
'(tags-revert-without-query t) '(tags-revert-without-query t)
'(tramp-completion-reread-directory-timeout nil) '(tramp-completion-reread-directory-timeout nil)
'(tramp-default-method "sshx") '(tramp-default-method "sshx")
'(tramp-use-connection-share nil)
'(tramp-use-ssh-controlmaster-options nil) '(tramp-use-ssh-controlmaster-options nil)
'(transient-mark-mode t) '(transient-mark-mode t)
'(truncate-lines t) '(truncate-lines t)

View file

@ -1401,24 +1401,44 @@ Do this when you edit your project view."
;; ================================================================= ;; =================================================================
;; AI Shit ;; AI Shit
;; ================================================================= ;; =================================================================
(defun claude-get-api-key () (use-package request :ensure)
"Get Claude API key from auth-source."
(let ((auth-info (nth 0 (auth-source-search :host "anthropic.com"
:user "claude-api"
:require '(:secret)
:create t))))
(if auth-info
(let ((secret (plist-get auth-info :secret)))
(if (functionp secret)
(funcall secret)
secret))
(error "Claude API key not found in auth-source"))))
(use-package gptel :ensure (use-package claude
:load-path "~/site-lisp/"
:custom
(claude-model "claude-3-7-sonnet-20250219") ;; Current model as of March 2025
(claude-max-tokens 4000)
(claude-auto-display-results t)
:config :config
(setq ;; If you want to add any custom tools, add them here
gptel-model 'claude-3-7-sonnet-20250219 ; "claude-3-opus-20240229" also available ;; (claude-register-tool
gptel-backend (gptel-make-anthropic "Claude" ;; '(:name "my_custom_tool"
:stream t :key #'claude-get-api-key)) ;; :description "A custom tool that does something specific"
) ;; :parameters ((properties
;; (param1 (title . "Parameter 1")
;; (type . "string")
;; (description . "Description of parameter 1"))))))
;; (claude-register-tool-handler
;; "my_custom_tool"
;; (lambda (parameters)
;; ;; Your implementation here
;; (format "Tool executed with param: %s" (cdr (assoc 'param1 parameters)))))
;; Enable the minor mode for keybindings
(claude-mode 1)
:bind (:map claude-mode-map
;; You can customize the keybindings if you prefer different ones
("C-c C-a a" . claude-prompt-and-send) ;; Add a custom binding
;; Default bindings included by claude-mode:
;; C-c C-a s - claude-send-region
;; C-c C-a b - claude-send-buffer
;; C-c C-a r - claude-code-review
;; C-c C-a e - claude-explain-code
;; C-c C-a c - claude-complete-code
;; C-c C-a t - claude-send-with-tools
;; C-c C-a l - claude-list-requested-tools
;; C-c C-a p - claude-prompt-and-send
))
;;; init.el ends here ;;; init.el ends here

View file

@ -4,14 +4,12 @@
;; Version: 1.0.0 ;; Version: 1.0.0
;; Package-Requires: ((emacs "27.1") (request "0.3.0") (json "1.5")) ;; Package-Requires: ((emacs "27.1") (request "0.3.0") (json "1.5"))
;; Keywords: tools, ai, assistant ;; Keywords: tools, ai, assistant
;; URL: https://git.d0ty.me/DeCarabas/claude-emacs
;;; Commentary: ;;; Commentary:
;; This package provides integration with the Claude AI assistant from ;; This package provides integration with the Claude AI assistant from Anthropic.
;; Anthropic. It includes basic functionality like sending text to Claude, as ;; It includes basic functionality like sending text to Claude, as well as
;; well as code-specific features and tool use capabilities. ;; code-specific features and tool use capabilities.
;;
;; It now features a conversational interface with support for context buffers
;; and optional tool use.
;;; Code: ;;; Code:
@ -40,77 +38,29 @@
:type 'boolean :type 'boolean
:group 'claude) :group 'claude)
(defcustom claude-conversation-buffer-name "*Claude Conversation*"
"Name of the buffer for Claude conversations."
:type 'string
:group 'claude)
(defcustom claude-enable-markdown-fontification t
"If non-nil, enable markdown fontification in the conversation buffer."
:type 'boolean
:group 'claude)
(defcustom claude-user-message-face 'font-lock-keyword-face
"Face for user messages in the conversation buffer."
:type 'face
:group 'claude)
(defcustom claude-assistant-message-face 'font-lock-comment-face
"Face for Claude's messages in the conversation buffer."
:type 'face
:group 'claude)
(defcustom claude-timestamp-face 'font-lock-doc-face
"Face for timestamps in the conversation buffer."
:type 'face
:group 'claude)
(defcustom claude-separator-string "────────────────────────────────────────────────────────────"
"Separator string used between messages in the conversation buffer."
:type 'string
:group 'claude)
;;; Core functionality: ;;; Core functionality:
(defvar claude-conversation-history nil (defvar claude-buffer-name "*Claude*"
"History of conversation with Claude as a list of alists with keys 'role' and 'content'.") "Name of the buffer for Claude interactions.")
(defvar claude-context-buffers nil (defvar claude-last-request nil
"List of buffers to provide as context for the conversation.") "Store the last request data sent to Claude.")
(defvar claude-tools-enabled nil (defvar claude-response-mode-map
"If non-nil, enable tool use in the conversation.")
(defvar claude-conversation-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") 'claude-send-input) (define-key map "q" 'quit-window)
(define-key map (kbd "C-c C-c") 'claude-send-input) (define-key map "r" 'claude-refresh-last-request) ;; We'll define this function later
(define-key map (kbd "C-c C-k") 'claude-cancel-input)
;; Change these keybindings to avoid conflict with the global prefix
(define-key map (kbd "C-c a") 'claude-add-context-buffer) ;; was C-c C-a
(define-key map (kbd "C-c r") 'claude-remove-context-buffer) ;; was C-c C-r
(define-key map (kbd "C-c l") 'claude-list-context-buffers) ;; was C-c C-l
(define-key map (kbd "C-c t") 'claude-toggle-tool-use) ;; was C-c C-t
(define-key map (kbd "C-c n") 'claude-new-conversation) ;; was C-c C-n
map) map)
"Keymap for Claude conversation buffers.") "Keymap for Claude response buffers.")
(define-derived-mode claude-conversation-mode text-mode "Claude Conversation" (define-derived-mode claude-response-mode markdown-mode "Claude"
"Major mode for conversing with Claude AI assistant." "Major mode for viewing Claude AI responses."
(use-local-map claude-response-mode-map)
;; Enable visual line mode for word wrapping ;; Enable visual line mode for word wrapping
(visual-line-mode 1) (visual-line-mode 1)
(setq-local indent-tabs-mode nil) ;; Make buffer read-only by default
(setq buffer-read-only t))
(use-local-map claude-conversation-mode-map)
(when claude-enable-markdown-fontification
(when (fboundp 'markdown-mode)
;; We only want the font-lock features of markdown mode
(setq-local font-lock-defaults (cadr (assoc 'font-lock-defaults
(symbol-plist 'markdown-mode))))))
;; Make the bottom of the buffer input-only
(setq-local claude-input-marker (point-max-marker))
(set-marker-insertion-type claude-input-marker nil)) ;; ?
(defun claude-get-api-key () (defun claude-get-api-key ()
"Get Claude API key from auth-source." "Get Claude API key from auth-source."
@ -125,33 +75,23 @@
secret)) secret))
(error "Claude API key not found in auth-source")))) (error "Claude API key not found in auth-source"))))
(defun claude-ensure-conversation-buffer () (defun claude-ensure-buffer ()
"Ensure the Claude conversation buffer exists and return it." "Ensure the Claude buffer exists with proper formatting and keybindings."
(let ((buffer (get-buffer-create claude-conversation-buffer-name))) (let ((buffer (get-buffer-create claude-buffer-name)))
(with-current-buffer buffer (with-current-buffer buffer
(unless (eq major-mode 'claude-conversation-mode) (unless (eq major-mode 'claude-response-mode)
(claude-conversation-mode) ;; Use our custom mode that has the q key binding
(claude-insert-conversation-header))) (if (fboundp 'markdown-mode) ;; Check if markdown-mode is available
buffer)) (claude-response-mode) ;; Use our derived mode if markdown is available
;; Fallback if markdown-mode isn't available
(special-mode) ;; special-mode also has the q key binding
(visual-line-mode 1))
(defun claude-insert-conversation-header () ;; Set word-wrap and margins regardless of mode
"Insert the header for a new conversation." (setq word-wrap t)
(let ((inhibit-read-only t)) (setq left-margin-width 2
(erase-buffer) right-margin-width 2)))
(insert (propertize "Claude Conversation" 'face 'bold) "\n\n") buffer))
(insert "Type your message below and press RET or C-c C-c to send.\n")
(insert "Commands:\n")
(insert " C-c a: Add a context buffer\n") ;; was C-c C-a
(insert " C-c r: Remove a context buffer\n") ;; was C-c C-r
(insert " C-c l: List context buffers\n") ;; was C-c C-l
(insert " C-c t: Toggle tool use\n") ;; was C-c C-t
(insert " C-c n: Start a new conversation\n") ;; was C-c C-n
(insert "\n")
(insert (propertize claude-separator-string 'face 'shadow))
(insert "\n\n")
;; Set the input marker at the end of the buffer
(setq-local claude-input-marker (point-marker))
(set-marker-insertion-type claude-input-marker nil)))
(defun claude-send-request (data &optional callback error-callback) (defun claude-send-request (data &optional callback error-callback)
"Send request with DATA to Claude API. "Send request with DATA to Claude API.
@ -176,237 +116,158 @@ If ERROR-CALLBACK is provided, call it with any error."
(funcall error-callback error-thrown) (funcall error-callback error-thrown)
(message "Claude API error: %s" error-thrown))))))) (message "Claude API error: %s" error-thrown)))))))
(defun claude-append-to-conversation (role content) (defun claude-display-response (data)
"Append a message with ROLE and CONTENT to the conversation history." "Display the response DATA from Claude with nice formatting."
(add-to-list 'claude-conversation-history (let ((content (cdr (assoc 'content data)))
`((role . ,role) (buffer (claude-ensure-buffer)))
(content . ,content))
t))
(defun claude-display-user-message (message)
"Display the user MESSAGE in the conversation buffer."
(let ((buffer (claude-ensure-conversation-buffer))
(timestamp (format-time-string "%H:%M:%S")))
(with-current-buffer buffer (with-current-buffer buffer
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
(save-excursion ;; Clear the buffer
(goto-char claude-input-marker) (erase-buffer)
(insert "\n")
(insert (propertize (format "[You - %s]" timestamp)
'face claude-timestamp-face))
(insert "\n\n")
(insert (propertize message 'face claude-user-message-face))
(insert "\n\n")
(insert (propertize claude-separator-string 'face 'shadow))
(insert "\n\n")))
(setq-local claude-input-marker (point-max-marker)))))
(defun claude-display-assistant-message (content) ;; Add a timestamp header
"Display the assistant's CONTENT in the conversation buffer." (insert (propertize
(let ((buffer (claude-ensure-conversation-buffer)) (format "Claude response at %s\n\n"
(timestamp (format-time-string "%H:%M:%S"))) (format-time-string "%H:%M:%S"))
(with-current-buffer buffer 'face 'font-lock-comment-face))
(let ((inhibit-read-only t))
(save-excursion
(goto-char claude-input-marker)
(insert "\n")
(insert (propertize (format "[Claude - %s]" timestamp)
'face claude-timestamp-face))
(insert "\n\n")
(insert (propertize content 'face claude-assistant-message-face))
(insert "\n\n")
(insert (propertize claude-separator-string 'face 'shadow))
(insert "\n\n")))
(setq-local claude-input-marker (point-max-marker))
(goto-char (point-max)))))
(defun claude-display-loading-indicator ()
"Display a loading indicator in the conversation buffer."
(let ((buffer (claude-ensure-conversation-buffer))
(timestamp (format-time-string "%H:%M:%S")))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(save-excursion
(goto-char claude-input-marker)
(insert "\n")
(insert (propertize (format "[Claude - %s]" timestamp)
'face claude-timestamp-face))
(insert "\n\n")
(insert (propertize "Thinking..." 'face 'italic))
;; Don't insert separator yet
(insert "\n\n")
(setq-local claude-loading-indicator-point (point-marker))
(set-marker-insertion-type claude-loading-indicator-point t))))))
(defun claude-remove-loading-indicator ()
"Remove the loading indicator from the conversation buffer."
(when (and (boundp 'claude-loading-indicator-point)
claude-loading-indicator-point)
(let ((buffer (claude-ensure-conversation-buffer)))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(save-excursion
(goto-char claude-loading-indicator-point)
(forward-line -3) ;; Go to the start of the loading message
(delete-region (point) claude-loading-indicator-point)))))))
(defun claude-process-response (data)
"Process and display the response DATA from Claude."
(let ((content (cdr (assoc 'content data))))
(claude-remove-loading-indicator)
;; Process content
(if (and content (arrayp content) (> (length content) 0)) (if (and content (arrayp content) (> (length content) 0))
(let ((response-text ""))
;; Collect text content from all response parts
(dotimes (i (length content)) (dotimes (i (length content))
(let* ((item (aref content i)) (let* ((item (aref content i))
(type (cdr (assoc 'type item)))) (type (cdr (assoc 'type item))))
(when (string= type "text") (when (string= type "text")
(let ((text (cdr (assoc 'text item)))) (let ((text (cdr (assoc 'text item))))
(setq response-text (concat response-text text)))))) (insert text "\n\n")))))
;; Add to conversation history
(claude-append-to-conversation "assistant" response-text)
;; Display the response
(claude-display-assistant-message response-text))
;; Handle unexpected response format ;; Handle unexpected response format
(let ((error-message "Received an unexpected response format from Claude API.")) (insert (propertize "Unexpected response format from Claude API.\n"
(claude-display-assistant-message 'face 'font-lock-warning-face))
(concat error-message "\n\nResponse data: " (prin1-to-string data)))))) (insert "Response data: " (prin1-to-string data)))
;; Move to input area ;; Add helpful instructions at the bottom
(with-current-buffer (claude-ensure-conversation-buffer) (goto-char (point-max))
(goto-char (point-max)))) (insert (propertize "\n──────────────────────────────────────\n"
'face 'font-lock-comment-face))
(insert (propertize "Press q to close this window\n"
'face 'font-lock-comment-face))
(defun claude-get-context-content () ;; Return to the start of the buffer
"Get the content from all context buffers." (goto-char (point-min))))
(let ((context-content ""))
(dolist (buffer claude-context-buffers)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq context-content
(concat context-content
(format "\n\nContent from buffer '%s':\n\n%s"
(buffer-name buffer)
(buffer-substring-no-properties (point-min) (point-max))))))))
context-content))
(defun claude-send-message (prompt) ;; Only display the buffer if requested and not already visible
"Send PROMPT to Claude and display the response in the conversation buffer." (when claude-auto-display-results
;; Add user message to history (unless (get-buffer-window buffer)
(claude-append-to-conversation "user" prompt) (display-buffer-other-window buffer)))))
;; Display the user's message (defun claude-send-message (prompt &optional system-prompt tools)
(claude-display-user-message prompt) "Send PROMPT to Claude and display the response.
If SYSTEM-PROMPT is provided, include it in the request.
;; Show loading indicator If TOOLS is provided, enable tool use."
(claude-display-loading-indicator) (let ((data `((model . ,claude-model)
;; Prepare messages for the API
(let* ((context (claude-get-context-content))
(final-prompt (if (string-empty-p context)
prompt
(concat prompt "\n\n" context)))
(messages-array (vconcat
(apply #'vector
(mapcar (lambda (msg)
`((role . ,(cdr (assoc 'role msg)))
(content . ,(cdr (assoc 'content msg)))))
claude-conversation-history))))
(data `((model . ,claude-model)
(max_tokens . ,claude-max-tokens) (max_tokens . ,claude-max-tokens)
(messages . ,messages-array)))) (messages . [((role . "user")
(content . ,prompt))]))))
;; Add tools if enabled ;; Add system prompt if provided
(when claude-tools-enabled (when system-prompt
(setq data (append data `((tools . ,(mapcar (lambda (tool) (setq data (append data `((system . ,system-prompt)))))
`((name . ,(plist-get tool :name))
(description . ,(plist-get tool :description)) ;; Add tools if provided
(parameters . ,(plist-get tool :parameters)))) (when tools
claude-tools)) (setq data (append data `((tools . ,tools)
(tool_choice . "auto"))))) (tool_choice . "auto")))))
;; Send the request ;; Display the buffer with a loading message
(let ((buffer (claude-ensure-buffer)))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Sending request to Claude...\n\n")))
(when claude-auto-display-results
(display-buffer buffer)))
;; Send request
(claude-send-request (claude-send-request
data data
(lambda (data) (lambda (data)
(if (and claude-tools-enabled (if tools
(or (assoc 'tool_use (aref (cdr (assoc 'content (aref (cdr (assoc 'messages data)) 0))) 0)) (claude-handle-tool-response data (claude-ensure-buffer))
(assoc 'tool_result (aref (cdr (assoc 'content (aref (cdr (assoc 'messages data)) 0))) 0)))) (claude-display-response data)))
(claude-handle-tool-response data (claude-ensure-conversation-buffer))
(claude-process-response data)))
(lambda (error-thrown) (lambda (error-thrown)
(claude-remove-loading-indicator) (let ((buffer (claude-ensure-buffer)))
(claude-display-assistant-message (format "Error: %s" error-thrown))))))
(defun claude-send-input ()
"Send the current input to Claude."
(interactive)
(let ((buffer (claude-ensure-conversation-buffer)))
(with-current-buffer buffer
(let ((input (buffer-substring-no-properties claude-input-marker (point-max))))
(when (not (string-empty-p (string-trim input)))
;; Delete the input text
(let ((inhibit-read-only t))
(delete-region claude-input-marker (point-max)))
;; Send the message
(claude-send-message (string-trim input)))))))
(defun claude-cancel-input ()
"Cancel the current input."
(interactive)
(let ((buffer (claude-ensure-conversation-buffer)))
(with-current-buffer buffer (with-current-buffer buffer
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
(delete-region claude-input-marker (point-max)))))) (erase-buffer)
(insert (format "Error: %s" error-thrown)))))))))
(defun claude-new-conversation () ;;; Interactive commands:
"Start a new conversation with Claude."
(defun claude-send-region (start end)
"Send the region between START and END to Claude and display the response."
(interactive "r")
(let ((prompt (buffer-substring-no-properties start end)))
(claude-send-message prompt)))
(defun claude-send-buffer ()
"Send the entire buffer to Claude."
(interactive) (interactive)
(setq claude-conversation-history nil) (claude-send-region (point-min) (point-max)))
(setq claude-context-buffers nil)
(let ((buffer (claude-ensure-conversation-buffer))) (defun claude-code-review ()
"Ask Claude to review the code in the current buffer."
(interactive)
(let ((code (buffer-substring-no-properties (point-min) (point-max))))
(claude-send-message
(concat "Please review the following code and suggest improvements:\n\n```\n"
code "\n```"))))
(defun claude-explain-code ()
"Ask Claude to explain the selected code."
(interactive)
(if (use-region-p)
(let ((code (buffer-substring-no-properties (region-beginning) (region-end))))
(claude-send-message
(concat "Please explain what this code does in detail:\n\n```\n"
code "\n```")))
(message "No region selected")))
(defun claude-complete-code ()
"Ask Claude to complete the code at point."
(interactive)
(let* ((buffer-text (buffer-substring-no-properties (point-min) (point-max)))
(cursor-pos (point))
(text-before (buffer-substring-no-properties (point-min) cursor-pos))
(text-after (buffer-substring-no-properties cursor-pos (point-max))))
(claude-send-message
(concat "I'm writing code and need you to continue it from where the cursor is marked with [CURSOR]. Only provide the code that should replace [CURSOR], nothing else.\n\n```\n"
text-before "[CURSOR]" text-after "\n```"))))
(defun claude-prompt-and-send ()
"Prompt for input and send to Claude."
(interactive)
(let ((prompt (read-string "Ask Claude: ")))
(claude-send-message prompt)))
(defun claude-refresh-last-request ()
"Refresh the last Claude request by sending it again."
(interactive)
(if claude-last-request
(let ((prompt (plist-get claude-last-request :prompt))
(system-prompt (plist-get claude-last-request :system-prompt))
(tools (plist-get claude-last-request :tools)))
;; Display refreshing message
(let ((buffer (claude-ensure-buffer)))
(with-current-buffer buffer (with-current-buffer buffer
(claude-insert-conversation-header))) (let ((inhibit-read-only t))
(message "Started a new conversation with Claude")) (erase-buffer)
(insert "Refreshing last request to Claude...\n\n"))))
(defun claude-add-context-buffer (buffer) ;; Re-send the same request
"Add BUFFER to the list of context buffers." (claude-send-message prompt system-prompt tools)
(interactive (list (read-buffer "Add buffer as context: " (other-buffer (current-buffer) t)))) (message "Refreshing Claude request..."))
(let ((buf (get-buffer buffer))) (message "No previous Claude request to refresh")))
(when buf
(unless (memq buf claude-context-buffers)
(add-to-list 'claude-context-buffers buf)
(message "Added %s to context buffers" (buffer-name buf))))))
(defun claude-remove-context-buffer (buffer)
"Remove BUFFER from the list of context buffers."
(interactive
(list
(when claude-context-buffers
(let ((buffer-names (mapcar #'buffer-name claude-context-buffers)))
(completing-read "Remove buffer from context: " buffer-names nil t)))))
(when buffer
(let ((buf (get-buffer buffer)))
(when buf
(setq claude-context-buffers (delq buf claude-context-buffers))
(message "Removed %s from context buffers" (buffer-name buf))))))
(defun claude-list-context-buffers ()
"List all context buffers in the minibuffer."
(interactive)
(if claude-context-buffers
(let ((buffer-list (mapconcat #'buffer-name claude-context-buffers ", ")))
(message "Context buffers: %s" buffer-list))
(message "No context buffers set")))
(defun claude-toggle-tool-use ()
"Toggle the use of tools in the conversation."
(interactive)
(setq claude-tools-enabled (not claude-tools-enabled))
(message "Claude tool use %s" (if claude-tools-enabled "enabled" "disabled")))
;;; Tool use functionality: ;;; Tool use functionality:
@ -440,25 +301,29 @@ The handler will be called with the parameters passed by Claude."
(funcall handler parameters) (funcall handler parameters)
(format "Error: No handler registered for tool %s" tool-name)))) (format "Error: No handler registered for tool %s" tool-name))))
(defun claude-send-with-tools (prompt)
"Send PROMPT to Claude with tools enabled and handle the response."
(interactive "sPrompt: ")
(let ((tools-json (mapcar (lambda (tool)
`((name . ,(plist-get tool :name))
(description . ,(plist-get tool :description))
(parameters . ,(plist-get tool :parameters))))
claude-tools)))
(claude-send-message prompt nil tools-json)))
(defun claude-handle-tool-response (data buffer) (defun claude-handle-tool-response (data buffer)
"Handle response DATA from Claude that may contain tool calls. "Handle response DATA from Claude that may contain tool calls.
Display results in BUFFER." Display results in BUFFER."
(let ((message (aref (cdr (assoc 'messages data)) 0))) (let ((message (aref (cdr (assoc 'messages data)) 0)))
(claude-remove-loading-indicator)
(with-current-buffer buffer (with-current-buffer buffer
(let ((content-items (cdr (assoc 'content message))) (let ((inhibit-read-only t))
(tool-usage-text "") (erase-buffer)
(response-text "")) (let ((content-items (cdr (assoc 'content message))))
(dolist (item content-items)
;; Process all content items (let ((type (cdr (assoc 'type item))))
(dotimes (i (length content-items))
(let* ((item (aref content-items i))
(type (cdr (assoc 'type item))))
(cond (cond
((string= type "text") ((string= type "text")
(setq response-text (insert (cdr (assoc 'text item)) "\n\n"))
(concat response-text (cdr (assoc 'text item)) "\n\n")))
((string= type "tool_use") ((string= type "tool_use")
(let* ((tool-use (cdr (assoc 'tool_use item))) (let* ((tool-use (cdr (assoc 'tool_use item)))
(tool-name (cdr (assoc 'name tool-use))) (tool-name (cdr (assoc 'name tool-use)))
@ -468,11 +333,9 @@ Display results in BUFFER."
;; Check if we have this tool ;; Check if we have this tool
(if handler (if handler
(let ((tool-result (claude-execute-tool tool-name parameters))) (let ((tool-result (claude-execute-tool tool-name parameters)))
(setq tool-usage-text (insert (format "Tool call: %s\n" tool-name))
(concat tool-usage-text (insert (format "Parameters: %s\n" (json-encode parameters)))
(format "Tool call: %s\n" tool-name) (insert (format "Result: %s\n\n" tool-result))
(format "Parameters: %s\n" (json-encode parameters))
(format "Result: %s\n\n" tool-result)))
;; Send the tool result back to Claude ;; Send the tool result back to Claude
(claude-send-tool-result data tool-name tool-result)) (claude-send-tool-result data tool-name tool-result))
@ -487,43 +350,21 @@ Display results in BUFFER."
;; Record the tool request ;; Record the tool request
(claude-request-tool tool-name description param-structure) (claude-request-tool tool-name description param-structure)
;; Add to response text ;; Notify Claude about missing tool
(setq tool-usage-text (insert (format "Tool requested: %s\n" tool-name))
(concat tool-usage-text (insert (format "This tool is not currently available.\n"))
(format "Tool requested: %s\n" tool-name) (insert "The request has been recorded. You can implement it with M-x claude-list-requested-tools.\n\n")
"This tool is not currently available.\n"
"The request has been recorded. You can implement it with M-x claude-list-requested-tools.\n\n"))
;; Send error message back to Claude ;; Send error message back to Claude
(let ((error-msg (format "The requested tool '%s' is not currently available. Would you like me to suggest an alternative approach?" tool-name))) (let ((error-msg (format "The requested tool '%s' is not currently available. Would you like me to suggest an alternative approach?" tool-name)))
(claude-send-tool-result data tool-name error-msg)))))))))) (claude-send-tool-result data tool-name error-msg))))))))))))))
;; Display the combined message
(let ((full-response (concat response-text
(if (not (string-empty-p tool-usage-text))
(concat "\n## Tool Usage\n\n" tool-usage-text)
""))))
(claude-append-to-conversation "assistant" full-response)
(claude-display-assistant-message full-response)))))
(defun claude-send-tool-result (data tool-name tool-result) (defun claude-send-tool-result (data tool-name tool-result)
"Send TOOL-RESULT for TOOL-NAME back to Claude based on the original DATA." "Send TOOL-RESULT for TOOL-NAME back to Claude based on the original DATA."
(let ((message-id (cdr (assoc 'id (aref (cdr (assoc 'messages data)) 0)))) (let ((message-id (cdr (assoc 'id (aref (cdr (assoc 'messages data)) 0))))
(tool-call-id nil) (tool-call-id (cdr (assoc 'id (cdr (assoc 'tool_use (aref (cdr (assoc 'content (aref (cdr (assoc 'messages data)) 0))) 0))))))
(api-key (claude-get-api-key)) (api-key (claude-get-api-key))
(buffer (claude-ensure-conversation-buffer))) (buffer (claude-ensure-buffer)))
;; Find the tool call ID
(let* ((content-items (cdr (assoc 'content (aref (cdr (assoc 'messages data)) 0)))))
(dotimes (i (length content-items))
(let* ((item (aref content-items i))
(type (cdr (assoc 'type item))))
(when (string= type "tool_use")
(setq tool-call-id (cdr (assoc 'id (cdr (assoc 'tool_use item)))))))))
(when tool-call-id
;; Show loading indicator
(claude-display-loading-indicator)
(request (request
"https://api.anthropic.com/v1/messages" "https://api.anthropic.com/v1/messages"
@ -545,8 +386,10 @@ Display results in BUFFER."
(claude-handle-tool-response data buffer))) (claude-handle-tool-response data buffer)))
:error (cl-function :error (cl-function
(lambda (&key error-thrown &allow-other-keys) (lambda (&key error-thrown &allow-other-keys)
(claude-remove-loading-indicator) (with-current-buffer buffer
(claude-display-assistant-message (format "Error: %s" error-thrown)))))))) (let ((inhibit-read-only t))
(erase-buffer)
(insert (format "Error: %s" error-thrown)))))))))
(defun claude-request-tool (tool-name description parameters) (defun claude-request-tool (tool-name description parameters)
"Record a request from Claude for a tool that isn't available." "Record a request from Claude for a tool that isn't available."
@ -793,92 +636,21 @@ Display results in BUFFER."
(claude-clear-tools) (claude-clear-tools)
(claude-register-filesystem-tools) (claude-register-filesystem-tools)
(claude-register-emacs-tools) (claude-register-emacs-tools)
;; Uncomment the line below if you want shell tools enabled by default
;; (claude-register-shell-tools) ;; (claude-register-shell-tools)
) )
;;; Interactive commands:
(defun claude-start-conversation ()
"Start or switch to a conversation with Claude."
(interactive)
(let ((buffer (claude-ensure-conversation-buffer)))
(switch-to-buffer buffer)))
(defun claude-send-region (start end)
"Send the region between START and END to Claude in the conversation buffer."
(interactive "r")
(let ((prompt (buffer-substring-no-properties start end)))
(claude-start-conversation)
(with-current-buffer (claude-ensure-conversation-buffer)
(goto-char claude-input-marker)
(insert prompt)
(claude-send-input))))
(defun claude-send-buffer ()
"Send the entire buffer to Claude in the conversation buffer."
(interactive)
(claude-send-region (point-min) (point-max)))
(defun claude-code-review ()
"Ask Claude to review the code in the current buffer."
(interactive)
(let ((code (buffer-substring-no-properties (point-min) (point-max)))
(buffer-name (buffer-name))
(buffer (current-buffer)))
;; Add the current buffer as context
(unless (memq buffer claude-context-buffers)
(add-to-list 'claude-context-buffers buffer))
;; Start the conversation
(claude-start-conversation)
(with-current-buffer (claude-ensure-conversation-buffer)
(goto-char claude-input-marker)
(insert (format "Please review the code in %s and suggest improvements." buffer-name))
(claude-send-input))))
(defun claude-explain-code ()
"Ask Claude to explain the selected code or current buffer."
(interactive)
(let ((buffer (current-buffer)))
;; Add the current buffer as context
(unless (memq buffer claude-context-buffers)
(add-to-list 'claude-context-buffers buffer))
;; Start the conversation
(claude-start-conversation)
(with-current-buffer (claude-ensure-conversation-buffer)
(goto-char claude-input-marker)
(if (use-region-p)
(insert "Please explain the selected code in detail.")
(insert (format "Please explain the code in %s in detail." (buffer-name buffer))))
(claude-send-input))))
(defun claude-complete-code ()
"Ask Claude to complete the code at point."
(interactive)
(let* ((buffer (current-buffer))
(cursor-pos (point))
(buffer-name (buffer-name)))
;; Add the current buffer as context
(unless (memq buffer claude-context-buffers)
(add-to-list 'claude-context-buffers buffer))
;; Start the conversation
(claude-start-conversation)
(with-current-buffer (claude-ensure-conversation-buffer)
(goto-char claude-input-marker)
(insert (format "Complete the code at the cursor position in %s. The cursor is at position %d."
buffer-name cursor-pos))
(claude-send-input))))
;;; Keybindings: ;;; Keybindings:
(defvar claude-mode-map (defvar claude-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-a c") 'claude-start-conversation)
(define-key map (kbd "C-c C-a s") 'claude-send-region) (define-key map (kbd "C-c C-a s") 'claude-send-region)
(define-key map (kbd "C-c C-a b") 'claude-send-buffer) (define-key map (kbd "C-c C-a b") 'claude-send-buffer)
(define-key map (kbd "C-c C-a r") 'claude-code-review) (define-key map (kbd "C-c C-a r") 'claude-code-review)
(define-key map (kbd "C-c C-a e") 'claude-explain-code) (define-key map (kbd "C-c C-a e") 'claude-explain-code)
(define-key map (kbd "C-c C-a o") 'claude-complete-code) (define-key map (kbd "C-c C-a c") 'claude-complete-code)
(define-key map (kbd "C-c C-a t") 'claude-send-with-tools)
(define-key map (kbd "C-c C-a l") 'claude-list-requested-tools)
(define-key map (kbd "C-c C-a p") 'claude-prompt-and-send)
map) map)
"Keymap for Claude mode.") "Keymap for Claude mode.")