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 () ;; Process content
"Display a loading indicator in the conversation buffer." (if (and content (arrayp content) (> (length content) 0))
(let ((buffer (claude-ensure-conversation-buffer)) (dotimes (i (length content))
(timestamp (format-time-string "%H:%M:%S"))) (let* ((item (aref content i))
(with-current-buffer buffer (type (cdr (assoc 'type item))))
(let ((inhibit-read-only t)) (when (string= type "text")
(save-excursion (let ((text (cdr (assoc 'text item))))
(goto-char claude-input-marker) (insert text "\n\n")))))
(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 () ;; Handle unexpected response format
"Remove the loading indicator from the conversation buffer." (insert (propertize "Unexpected response format from Claude API.\n"
(when (and (boundp 'claude-loading-indicator-point) 'face 'font-lock-warning-face))
claude-loading-indicator-point) (insert "Response data: " (prin1-to-string data)))
(let ((buffer (claude-ensure-conversation-buffer)))
;; Add helpful instructions at the bottom
(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))
;; Return to the start of the buffer
(goto-char (point-min))))
;; Only display the buffer if requested and not already visible
(when claude-auto-display-results
(unless (get-buffer-window buffer)
(display-buffer-other-window buffer)))))
(defun claude-send-message (prompt &optional system-prompt tools)
"Send PROMPT to Claude and display the response.
If SYSTEM-PROMPT is provided, include it in the request.
If TOOLS is provided, enable tool use."
(let ((data `((model . ,claude-model)
(max_tokens . ,claude-max-tokens)
(messages . [((role . "user")
(content . ,prompt))]))))
;; Add system prompt if provided
(when system-prompt
(setq data (append data `((system . ,system-prompt)))))
;; Add tools if provided
(when tools
(setq data (append data `((tools . ,tools)
(tool_choice . "auto")))))
;; Display the buffer with a loading message
(let ((buffer (claude-ensure-buffer)))
(with-current-buffer buffer (with-current-buffer buffer
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
(save-excursion (erase-buffer)
(goto-char claude-loading-indicator-point) (insert "Sending request to Claude...\n\n")))
(forward-line -3) ;; Go to the start of the loading message (when claude-auto-display-results
(delete-region (point) claude-loading-indicator-point))))))) (display-buffer buffer)))
(defun claude-process-response (data) ;; Send request
"Process and display the response DATA from Claude."
(let ((content (cdr (assoc 'content data))))
(claude-remove-loading-indicator)
(if (and content (arrayp content) (> (length content) 0))
(let ((response-text ""))
;; Collect text content from all response parts
(dotimes (i (length content))
(let* ((item (aref content i))
(type (cdr (assoc 'type item))))
(when (string= type "text")
(let ((text (cdr (assoc 'text item))))
(setq response-text (concat response-text text))))))
;; Add to conversation history
(claude-append-to-conversation "assistant" response-text)
;; Display the response
(claude-display-assistant-message response-text))
;; Handle unexpected response format
(let ((error-message "Received an unexpected response format from Claude API."))
(claude-display-assistant-message
(concat error-message "\n\nResponse data: " (prin1-to-string data))))))
;; Move to input area
(with-current-buffer (claude-ensure-conversation-buffer)
(goto-char (point-max))))
(defun claude-get-context-content ()
"Get the content from all context buffers."
(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)
"Send PROMPT to Claude and display the response in the conversation buffer."
;; Add user message to history
(claude-append-to-conversation "user" prompt)
;; Display the user's message
(claude-display-user-message prompt)
;; Show loading indicator
(claude-display-loading-indicator)
;; 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)
(messages . ,messages-array))))
;; Add tools if enabled
(when claude-tools-enabled
(setq data (append data `((tools . ,(mapcar (lambda (tool)
`((name . ,(plist-get tool :name))
(description . ,(plist-get tool :description))
(parameters . ,(plist-get tool :parameters))))
claude-tools))
(tool_choice . "auto")))))
;; Send the 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)))))) (with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert (format "Error: %s" error-thrown)))))))))
(defun claude-send-input () ;;; Interactive commands:
"Send the current input to 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)
(let ((buffer (claude-ensure-conversation-buffer))) (claude-send-region (point-min) (point-max)))
(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 () (defun claude-code-review ()
"Cancel the current input." "Ask Claude to review the code in the current buffer."
(interactive) (interactive)
(let ((buffer (claude-ensure-conversation-buffer))) (let ((code (buffer-substring-no-properties (point-min) (point-max))))
(with-current-buffer buffer (claude-send-message
(let ((inhibit-read-only t)) (concat "Please review the following code and suggest improvements:\n\n```\n"
(delete-region claude-input-marker (point-max)))))) code "\n```"))))
(defun claude-new-conversation () (defun claude-explain-code ()
"Start a new conversation with Claude." "Ask Claude to explain the selected code."
(interactive) (interactive)
(setq claude-conversation-history nil) (if (use-region-p)
(setq claude-context-buffers nil) (let ((code (buffer-substring-no-properties (region-beginning) (region-end))))
(let ((buffer (claude-ensure-conversation-buffer))) (claude-send-message
(with-current-buffer buffer (concat "Please explain what this code does in detail:\n\n```\n"
(claude-insert-conversation-header))) code "\n```")))
(message "Started a new conversation with Claude")) (message "No region selected")))
(defun claude-add-context-buffer (buffer) (defun claude-complete-code ()
"Add BUFFER to the list of context buffers." "Ask Claude to complete the code at point."
(interactive (list (read-buffer "Add buffer as context: " (other-buffer (current-buffer) t))))
(let ((buf (get-buffer buffer)))
(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) (interactive)
(if claude-context-buffers (let* ((buffer-text (buffer-substring-no-properties (point-min) (point-max)))
(let ((buffer-list (mapconcat #'buffer-name claude-context-buffers ", "))) (cursor-pos (point))
(message "Context buffers: %s" buffer-list)) (text-before (buffer-substring-no-properties (point-min) cursor-pos))
(message "No context buffers set"))) (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-toggle-tool-use () (defun claude-prompt-and-send ()
"Toggle the use of tools in the conversation." "Prompt for input and send to Claude."
(interactive) (interactive)
(setq claude-tools-enabled (not claude-tools-enabled)) (let ((prompt (read-string "Ask Claude: ")))
(message "Claude tool use %s" (if claude-tools-enabled "enabled" "disabled"))) (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
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Refreshing last request to Claude...\n\n"))))
;; Re-send the same request
(claude-send-message prompt system-prompt tools)
(message "Refreshing Claude request..."))
(message "No previous Claude request to refresh")))
;;; Tool use functionality: ;;; Tool use functionality:
@ -440,113 +301,95 @@ 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)
(let ((type (cdr (assoc 'type item))))
(cond
((string= type "text")
(insert (cdr (assoc 'text item)) "\n\n"))
((string= type "tool_use")
(let* ((tool-use (cdr (assoc 'tool_use item)))
(tool-name (cdr (assoc 'name tool-use)))
(parameters (cdr (assoc 'parameters tool-use)))
(handler (gethash tool-name claude-tool-handlers)))
;; Process all content items ;; Check if we have this tool
(dotimes (i (length content-items)) (if handler
(let* ((item (aref content-items i)) (let ((tool-result (claude-execute-tool tool-name parameters)))
(type (cdr (assoc 'type item)))) (insert (format "Tool call: %s\n" tool-name))
(cond (insert (format "Parameters: %s\n" (json-encode parameters)))
((string= type "text") (insert (format "Result: %s\n\n" tool-result))
(setq response-text
(concat response-text (cdr (assoc 'text item)) "\n\n")))
((string= type "tool_use") ;; Send the tool result back to Claude
(let* ((tool-use (cdr (assoc 'tool_use item))) (claude-send-tool-result data tool-name tool-result))
(tool-name (cdr (assoc 'name tool-use)))
(parameters (cdr (assoc 'parameters tool-use)))
(handler (gethash tool-name claude-tool-handlers)))
;; Check if we have this tool ;; Tool not found - record the request and notify
(if handler (let ((description (format "Tool requested by Claude for task: %s"
(let ((tool-result (claude-execute-tool tool-name parameters))) (or (cdr (assoc 'description tool-use))
(setq tool-usage-text "No description provided")))
(concat tool-usage-text (param-structure (or (cdr (assoc 'parameter_structure tool-use))
(format "Tool call: %s\n" tool-name) parameters)))
(format "Parameters: %s\n" (json-encode parameters))
(format "Result: %s\n\n" tool-result)))
;; Send the tool result back to Claude ;; Record the tool request
(claude-send-tool-result data tool-name tool-result)) (claude-request-tool tool-name description param-structure)
;; Tool not found - record the request and notify ;; Notify Claude about missing tool
(let ((description (format "Tool requested by Claude for task: %s" (insert (format "Tool requested: %s\n" tool-name))
(or (cdr (assoc 'description tool-use)) (insert (format "This tool is not currently available.\n"))
"No description provided"))) (insert "The request has been recorded. You can implement it with M-x claude-list-requested-tools.\n\n")
(param-structure (or (cdr (assoc 'parameter_structure tool-use))
parameters)))
;; Record the tool request ;; Send error message back to Claude
(claude-request-tool tool-name description param-structure) (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))))))))))))))
;; Add to response text
(setq tool-usage-text
(concat tool-usage-text
(format "Tool requested: %s\n" tool-name)
"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
(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))))))))))
;; 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 (request
(let* ((content-items (cdr (assoc 'content (aref (cdr (assoc 'messages data)) 0))))) "https://api.anthropic.com/v1/messages"
(dotimes (i (length content-items)) :type "POST"
(let* ((item (aref content-items i)) :headers `(("Content-Type" . "application/json")
(type (cdr (assoc 'type item)))) ("x-api-key" . ,api-key)
(when (string= type "tool_use") ("anthropic-version" . "2023-06-01"))
(setq tool-call-id (cdr (assoc 'id (cdr (assoc 'tool_use item))))))))) :data (json-encode
`((model . ,claude-model)
(when tool-call-id (max_tokens . ,claude-max-tokens)
;; Show loading indicator (messages . ,(vconcat (cdr (assoc 'messages data))
(claude-display-loading-indicator) `[((role . "assistant")
(content . [((type . "tool_result")
(request (tool_result . ((tool_call_id . ,tool-call-id)
"https://api.anthropic.com/v1/messages" (content . ,tool-result))))]))]))))
:type "POST" :parser 'json-read
:headers `(("Content-Type" . "application/json") :success (cl-function
("x-api-key" . ,api-key) (lambda (&key data &allow-other-keys)
("anthropic-version" . "2023-06-01")) (claude-handle-tool-response data buffer)))
:data (json-encode :error (cl-function
`((model . ,claude-model) (lambda (&key error-thrown &allow-other-keys)
(max_tokens . ,claude-max-tokens) (with-current-buffer buffer
(messages . ,(vconcat (cdr (assoc 'messages data)) (let ((inhibit-read-only t))
`[((role . "assistant") (erase-buffer)
(content . [((type . "tool_result") (insert (format "Error: %s" error-thrown)))))))))
(tool_result . ((tool_call_id . ,tool-call-id)
(content . ,tool-result))))]))]))))
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(claude-handle-tool-response data buffer)))
:error (cl-function
(lambda (&key error-thrown &allow-other-keys)
(claude-remove-loading-indicator)
(claude-display-assistant-message (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.")