[emacs] Claude integration?
Written by Claude itself, mostly!
This commit is contained in:
parent
1e292de616
commit
3ac73bb3e2
2 changed files with 728 additions and 13 deletions
688
site-lisp/claude.el
Normal file
688
site-lisp/claude.el
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
;;; claude.el --- Integration with Claude AI assistant -*- lexical-binding: t -*-
|
||||
|
||||
;; Author: John Doty <john@d0ty.me>; mostly generated by claude.ai
|
||||
;; Version: 1.0.0
|
||||
;; Package-Requires: ((emacs "27.1") (request "0.3.0") (json "1.5"))
|
||||
;; Keywords: tools, ai, assistant
|
||||
;; URL: https://git.d0ty.me/DeCarabas/claude-emacs
|
||||
|
||||
;;; Commentary:
|
||||
;; This package provides integration with the Claude AI assistant from Anthropic.
|
||||
;; It includes basic functionality like sending text to Claude, as well as
|
||||
;; code-specific features and tool use capabilities.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'request)
|
||||
(require 'json)
|
||||
(require 'auth-source)
|
||||
|
||||
;;; Customization:
|
||||
|
||||
(defgroup claude nil
|
||||
"Integration with Claude AI assistant."
|
||||
:group 'tools)
|
||||
|
||||
(defcustom claude-model "claude-3-7-sonnet-20250219"
|
||||
"Claude model to use."
|
||||
:type 'string
|
||||
:group 'claude)
|
||||
|
||||
(defcustom claude-max-tokens 4000
|
||||
"Maximum number of tokens in Claude's response."
|
||||
:type 'integer
|
||||
:group 'claude)
|
||||
|
||||
(defcustom claude-auto-display-results t
|
||||
"If non-nil, automatically display Claude's responses."
|
||||
:type 'boolean
|
||||
:group 'claude)
|
||||
|
||||
;;; Core functionality:
|
||||
|
||||
(defvar claude-buffer-name "*Claude*"
|
||||
"Name of the buffer for Claude interactions.")
|
||||
|
||||
(defvar claude-conversation-history nil
|
||||
"History of the current conversation with Claude.")
|
||||
|
||||
(defvar claude-response-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map "q" 'quit-window)
|
||||
(define-key map "r" 'claude-refresh-last-request) ;; We'll define this function later
|
||||
map)
|
||||
"Keymap for Claude response buffers.")
|
||||
|
||||
(define-derived-mode claude-response-mode markdown-mode "Claude"
|
||||
"Major mode for viewing Claude AI responses."
|
||||
(use-local-map claude-response-mode-map)
|
||||
;; Enable visual line mode for word wrapping
|
||||
(visual-line-mode 1)
|
||||
;; Make buffer read-only by default
|
||||
(setq buffer-read-only t))
|
||||
|
||||
|
||||
(defun claude-get-api-key ()
|
||||
"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"))))
|
||||
|
||||
(defun claude-ensure-buffer ()
|
||||
"Ensure the Claude buffer exists with proper formatting and keybindings."
|
||||
(let ((buffer (get-buffer-create claude-buffer-name)))
|
||||
(with-current-buffer buffer
|
||||
(unless (eq major-mode 'claude-response-mode)
|
||||
;; Use our custom mode that has the q key binding
|
||||
(if (fboundp 'markdown-mode) ;; Check if markdown-mode is available
|
||||
(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))
|
||||
|
||||
;; Set word-wrap and margins regardless of mode
|
||||
(setq word-wrap t)
|
||||
(setq left-margin-width 2
|
||||
right-margin-width 2)))
|
||||
buffer))
|
||||
|
||||
(defun claude-send-request (data &optional callback error-callback)
|
||||
"Send request with DATA to Claude API.
|
||||
If CALLBACK is provided, call it with the response data.
|
||||
If ERROR-CALLBACK is provided, call it with any error."
|
||||
(let ((api-key (claude-get-api-key)))
|
||||
(request
|
||||
"https://api.anthropic.com/v1/messages"
|
||||
:type "POST"
|
||||
:headers `(("Content-Type" . "application/json")
|
||||
("x-api-key" . ,api-key)
|
||||
("anthropic-version" . "2023-06-01"))
|
||||
:data (json-encode data)
|
||||
:parser 'json-read
|
||||
:success (cl-function
|
||||
(lambda (&key data &allow-other-keys)
|
||||
;; (message "Claude: API response structure: %S" data)
|
||||
(when callback
|
||||
(funcall callback data))))
|
||||
:error (cl-function
|
||||
(lambda (&key error-thrown &allow-other-keys)
|
||||
(if error-callback
|
||||
(funcall error-callback error-thrown)
|
||||
(message "Claude API error: %s" error-thrown)))))))
|
||||
|
||||
(defun claude-extract-text-content (message)
|
||||
"Extract text content from MESSAGE returned by the API."
|
||||
(let ((content-items (cdr (assoc 'content message)))
|
||||
(result ""))
|
||||
(dolist (item content-items)
|
||||
(let ((type (cdr (assoc 'type item))))
|
||||
(when (string= type "text")
|
||||
(setq result (concat result (cdr (assoc 'text item)) "\n\n")))))
|
||||
result))
|
||||
|
||||
(defun claude-display-response (data)
|
||||
"Display the response DATA from Claude with nice formatting."
|
||||
(let ((content (cdr (assoc 'content data)))
|
||||
(buffer (claude-ensure-buffer)))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
;; Clear the buffer
|
||||
(erase-buffer)
|
||||
|
||||
;; Add a timestamp header
|
||||
(insert (propertize
|
||||
(format "Claude response at %s\n\n"
|
||||
(format-time-string "%H:%M:%S"))
|
||||
'face 'font-lock-comment-face))
|
||||
|
||||
;; Process content
|
||||
(if (and content (arrayp content) (> (length content) 0))
|
||||
(dotimes (i (length content))
|
||||
(let* ((item (aref content i))
|
||||
(type (cdr (assoc 'type item))))
|
||||
(when (string= type "text")
|
||||
(let ((text (cdr (assoc 'text item))))
|
||||
(insert text "\n\n")))))
|
||||
|
||||
;; Handle unexpected response format
|
||||
(insert (propertize "Unexpected response format from Claude API.\n"
|
||||
'face 'font-lock-warning-face))
|
||||
(insert "Response data: " (prin1-to-string data)))
|
||||
|
||||
;; 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-display-response (data)
|
||||
;; "Display the response DATA from Claude."
|
||||
;; (let* ((message (aref (cdr (assoc 'messages data)) 0))
|
||||
;; (content (claude-extract-text-content message))
|
||||
;; (buffer (claude-ensure-buffer)))
|
||||
;; (with-current-buffer buffer
|
||||
;; (let ((inhibit-read-only t))
|
||||
;; (erase-buffer)
|
||||
;; (insert content)
|
||||
;; (goto-char (point-min))))
|
||||
;; (when claude-auto-display-results
|
||||
;; (display-buffer 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
|
||||
(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
|
||||
data
|
||||
(lambda (data)
|
||||
(if tools
|
||||
(claude-handle-tool-response data (claude-ensure-buffer))
|
||||
(claude-display-response data)))
|
||||
(lambda (error-thrown)
|
||||
(let ((buffer (claude-ensure-buffer)))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (format "Error: %s" error-thrown)))))))))
|
||||
|
||||
;;; Interactive commands:
|
||||
|
||||
(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)
|
||||
(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))))
|
||||
(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."
|
||||
(interactive)
|
||||
(message "Refresh functionality not yet implemented."))
|
||||
|
||||
;;; Tool use functionality:
|
||||
|
||||
(defvar claude-tools nil
|
||||
"List of tool definitions available to Claude.")
|
||||
|
||||
(defvar claude-tool-handlers (make-hash-table :test 'equal)
|
||||
"Hash table of tool handlers, keyed by tool name.")
|
||||
|
||||
(defvar claude-tool-requests nil
|
||||
"List of tools that Claude has requested but aren't registered.")
|
||||
|
||||
(defun claude-register-tool (tool-def)
|
||||
"Register a tool definition for Claude to use.
|
||||
TOOL-DEF should be a plist with :name, :description, and :parameters."
|
||||
(add-to-list 'claude-tools tool-def t))
|
||||
|
||||
(defun claude-clear-tools ()
|
||||
"Clear all registered tools."
|
||||
(setq claude-tools nil))
|
||||
|
||||
(defun claude-register-tool-handler (tool-name handler)
|
||||
"Register a HANDLER function for TOOL-NAME.
|
||||
The handler will be called with the parameters passed by Claude."
|
||||
(puthash tool-name handler claude-tool-handlers))
|
||||
|
||||
(defun claude-execute-tool (tool-name parameters)
|
||||
"Execute the tool with TOOL-NAME using PARAMETERS."
|
||||
(let ((handler (gethash tool-name claude-tool-handlers)))
|
||||
(if handler
|
||||
(funcall handler parameters)
|
||||
(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)
|
||||
"Handle response DATA from Claude that may contain tool calls.
|
||||
Display results in BUFFER."
|
||||
(let ((message (aref (cdr (assoc 'messages data)) 0)))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(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)))
|
||||
|
||||
;; Check if we have this tool
|
||||
(if handler
|
||||
(let ((tool-result (claude-execute-tool tool-name parameters)))
|
||||
(insert (format "Tool call: %s\n" tool-name))
|
||||
(insert (format "Parameters: %s\n" (json-encode parameters)))
|
||||
(insert (format "Result: %s\n\n" tool-result))
|
||||
|
||||
;; Send the tool result back to Claude
|
||||
(claude-send-tool-result data tool-name tool-result))
|
||||
|
||||
;; Tool not found - record the request and notify
|
||||
(let ((description (format "Tool requested by Claude for task: %s"
|
||||
(or (cdr (assoc 'description tool-use))
|
||||
"No description provided")))
|
||||
(param-structure (or (cdr (assoc 'parameter_structure tool-use))
|
||||
parameters)))
|
||||
|
||||
;; Record the tool request
|
||||
(claude-request-tool tool-name description param-structure)
|
||||
|
||||
;; Notify Claude about missing tool
|
||||
(insert (format "Tool requested: %s\n" tool-name))
|
||||
(insert (format "This tool is not currently available.\n"))
|
||||
(insert "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))))))))))))))
|
||||
|
||||
(defun claude-send-tool-result (data tool-name tool-result)
|
||||
"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))))
|
||||
(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))
|
||||
(buffer (claude-ensure-buffer)))
|
||||
|
||||
(request
|
||||
"https://api.anthropic.com/v1/messages"
|
||||
:type "POST"
|
||||
:headers `(("Content-Type" . "application/json")
|
||||
("x-api-key" . ,api-key)
|
||||
("anthropic-version" . "2023-06-01"))
|
||||
:data (json-encode
|
||||
`((model . ,claude-model)
|
||||
(max_tokens . ,claude-max-tokens)
|
||||
(messages . ,(vconcat (cdr (assoc 'messages data))
|
||||
`[((role . "assistant")
|
||||
(content . [((type . "tool_result")
|
||||
(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)
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (format "Error: %s" error-thrown)))))))))
|
||||
|
||||
(defun claude-request-tool (tool-name description parameters)
|
||||
"Record a request from Claude for a tool that isn't available."
|
||||
(add-to-list 'claude-tool-requests
|
||||
(list :name tool-name
|
||||
:description description
|
||||
:parameters parameters)
|
||||
t))
|
||||
|
||||
(defun claude-list-requested-tools ()
|
||||
"Show all tools that Claude has requested but aren't registered."
|
||||
(interactive)
|
||||
(let ((buffer (get-buffer-create "*Claude Tool Requests*")))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert "# Tools Requested by Claude\n\n")
|
||||
(if claude-tool-requests
|
||||
(dolist (tool claude-tool-requests)
|
||||
(insert (format "## %s\n\n" (plist-get tool :name)))
|
||||
(insert (format "Description: %s\n\n" (plist-get tool :description)))
|
||||
(insert "Parameters:\n")
|
||||
(let ((params (plist-get tool :parameters)))
|
||||
(dolist (param params)
|
||||
(insert (format "- %s: %s\n"
|
||||
(car param)
|
||||
(cdr (assoc 'description (cdr param)))))))
|
||||
(insert "\n"))
|
||||
(insert "No tools have been requested yet.\n"))
|
||||
(insert "\nUse M-x claude-implement-requested-tool to implement one of these tools.\n")))
|
||||
(switch-to-buffer buffer)))
|
||||
|
||||
(defun claude-implement-requested-tool (tool-name)
|
||||
"Provide a template to implement a requested tool."
|
||||
(interactive
|
||||
(list (completing-read "Select tool to implement: "
|
||||
(mapcar (lambda (tool) (plist-get tool :name))
|
||||
claude-tool-requests))))
|
||||
|
||||
(let* ((tool (car (cl-remove-if-not
|
||||
(lambda (t) (string= (plist-get t :name) tool-name))
|
||||
claude-tool-requests)))
|
||||
(name (plist-get tool :name))
|
||||
(description (plist-get tool :description))
|
||||
(parameters (plist-get tool :parameters))
|
||||
(buffer (get-buffer-create (format "*Implement Tool: %s*" name))))
|
||||
|
||||
(with-current-buffer buffer
|
||||
(erase-buffer)
|
||||
(emacs-lisp-mode)
|
||||
(insert (format ";; Implementation template for tool: %s\n\n" name))
|
||||
(insert "(claude-register-tool\n")
|
||||
(insert (format " '(:name \"%s\"\n" name))
|
||||
(insert (format " :description \"%s\"\n" description))
|
||||
(insert " :parameters ((properties\n")
|
||||
|
||||
;; Format parameters
|
||||
(dolist (param parameters)
|
||||
(let ((param-name (car param))
|
||||
(param-props (cdr param)))
|
||||
(insert (format " (%s (title . \"%s\")\n"
|
||||
param-name
|
||||
(or (cdr (assoc 'title param-props)) param-name)))
|
||||
(insert (format " (type . \"%s\")\n"
|
||||
(or (cdr (assoc 'type param-props)) "string")))
|
||||
(insert (format " (description . \"%s\")))\n"
|
||||
(or (cdr (assoc 'description param-props)) "")))))
|
||||
|
||||
(insert " )))\n\n")
|
||||
(insert "(claude-register-tool-handler\n")
|
||||
(insert (format " \"%s\"\n" name))
|
||||
(insert " (lambda (parameters)\n")
|
||||
(insert " ;; Extract parameters\n")
|
||||
|
||||
;; Parameter extraction
|
||||
(dolist (param parameters)
|
||||
(let ((param-name (car param)))
|
||||
(insert (format " (let ((%s (cdr (assoc '%s parameters))))\n"
|
||||
param-name param-name))))
|
||||
|
||||
(insert " ;; Your implementation here\n")
|
||||
(insert " ;; Return the result as a string or JSON-encodable object\n")
|
||||
(insert " )))\n"))
|
||||
|
||||
(switch-to-buffer buffer)))
|
||||
|
||||
;;; Built-in tools:
|
||||
|
||||
(defun claude-register-filesystem-tools ()
|
||||
"Register filesystem-related tools."
|
||||
(interactive)
|
||||
|
||||
;; List directory contents
|
||||
(claude-register-tool
|
||||
'(:name "list_directory"
|
||||
:description "List files and directories in a specified path"
|
||||
:parameters ((properties
|
||||
(path (title . "Path")
|
||||
(type . "string")
|
||||
(description . "Directory path to list"))))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"list_directory"
|
||||
(lambda (parameters)
|
||||
(let ((path (cdr (assoc 'path parameters))))
|
||||
(condition-case err
|
||||
(let ((files (directory-files-and-attributes path t)))
|
||||
(json-encode
|
||||
(mapcar (lambda (file)
|
||||
`((name . ,(file-name-nondirectory (car file)))
|
||||
(type . ,(if (cadr file) "directory" "file"))
|
||||
(size . ,(file-attribute-size (cdr file)))))
|
||||
files)))
|
||||
(error (format "Error listing directory: %s" (error-message-string err)))))))
|
||||
|
||||
;; Read file contents
|
||||
(claude-register-tool
|
||||
'(:name "read_file"
|
||||
:description "Read the contents of a file"
|
||||
:parameters ((properties
|
||||
(path (title . "Path")
|
||||
(type . "string")
|
||||
(description . "Path to the file to read"))))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"read_file"
|
||||
(lambda (parameters)
|
||||
(let ((path (cdr (assoc 'path parameters))))
|
||||
(condition-case err
|
||||
(with-temp-buffer
|
||||
(insert-file-contents path)
|
||||
(buffer-string))
|
||||
(error (format "Error reading file: %s" (error-message-string err)))))))
|
||||
|
||||
;; Write to file
|
||||
(claude-register-tool
|
||||
'(:name "write_file"
|
||||
:description "Write content to a file"
|
||||
:parameters ((properties
|
||||
(path (title . "Path")
|
||||
(type . "string")
|
||||
(description . "Path to the file to write"))
|
||||
(content (title . "Content")
|
||||
(type . "string")
|
||||
(description . "Content to write to the file"))))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"write_file"
|
||||
(lambda (parameters)
|
||||
(let ((path (cdr (assoc 'path parameters)))
|
||||
(content (cdr (assoc 'content parameters))))
|
||||
(condition-case err
|
||||
(progn
|
||||
(with-temp-file path
|
||||
(insert content))
|
||||
(format "Successfully wrote to %s" path))
|
||||
(error (format "Error writing to file: %s" (error-message-string err))))))))
|
||||
|
||||
(defun claude-register-emacs-tools ()
|
||||
"Register Emacs-specific tools."
|
||||
(interactive)
|
||||
|
||||
;; List buffers
|
||||
(claude-register-tool
|
||||
'(:name "list_buffers"
|
||||
:description "List all Emacs buffers"
|
||||
:parameters ((properties ()))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"list_buffers"
|
||||
(lambda (parameters)
|
||||
(json-encode
|
||||
(mapcar (lambda (buffer)
|
||||
`((name . ,(buffer-name buffer))
|
||||
(file . ,(or (buffer-file-name buffer) ""))
|
||||
(modified . ,(if (buffer-modified-p buffer) t :json-false))
|
||||
(size . ,(buffer-size buffer))))
|
||||
(buffer-list)))))
|
||||
|
||||
;; Get buffer content
|
||||
(claude-register-tool
|
||||
'(:name "get_buffer_content"
|
||||
:description "Get the content of an Emacs buffer"
|
||||
:parameters ((properties
|
||||
(buffer_name (title . "Buffer Name")
|
||||
(type . "string")
|
||||
(description . "Name of the buffer to get content from"))))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"get_buffer_content"
|
||||
(lambda (parameters)
|
||||
(let ((buffer-name (cdr (assoc 'buffer_name parameters))))
|
||||
(if (get-buffer buffer-name)
|
||||
(with-current-buffer buffer-name
|
||||
(buffer-string))
|
||||
(format "Buffer '%s' not found" buffer-name)))))
|
||||
|
||||
;; List installed packages
|
||||
(claude-register-tool
|
||||
'(:name "list_packages"
|
||||
:description "List installed Emacs packages"
|
||||
:parameters ((properties ()))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"list_packages"
|
||||
(lambda (parameters)
|
||||
(require 'package)
|
||||
(json-encode
|
||||
(mapcar (lambda (pkg)
|
||||
(let ((pkg-name (car pkg))
|
||||
(pkg-desc (cadr pkg)))
|
||||
`((name . ,(symbol-name pkg-name))
|
||||
(version . ,(package-version-join
|
||||
(package-desc-version pkg-desc)))
|
||||
(status . ,(if (package-built-in-p pkg-name)
|
||||
"built-in"
|
||||
"installed")))))
|
||||
package-alist)))))
|
||||
|
||||
(defun claude-register-shell-tools ()
|
||||
"Register shell command tool."
|
||||
(interactive)
|
||||
|
||||
;; Run shell command
|
||||
(claude-register-tool
|
||||
'(:name "run_shell_command"
|
||||
:description "Run a shell command and return its output"
|
||||
:parameters ((properties
|
||||
(command (title . "Command")
|
||||
(type . "string")
|
||||
(description . "Shell command to execute"))))))
|
||||
|
||||
(claude-register-tool-handler
|
||||
"run_shell_command"
|
||||
(lambda (parameters)
|
||||
(let ((command (cdr (assoc 'command parameters))))
|
||||
(condition-case err
|
||||
(shell-command-to-string command)
|
||||
(error (format "Error running command: %s" (error-message-string err))))))))
|
||||
|
||||
(defun claude-init-tools ()
|
||||
"Initialize all available tools."
|
||||
(interactive)
|
||||
(claude-clear-tools)
|
||||
(claude-register-filesystem-tools)
|
||||
(claude-register-emacs-tools)
|
||||
;; (claude-register-shell-tools)
|
||||
)
|
||||
|
||||
;;; Keybindings:
|
||||
|
||||
(defvar claude-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(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 r") 'claude-code-review)
|
||||
(define-key map (kbd "C-c C-a e") 'claude-explain-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)
|
||||
"Keymap for Claude mode.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode claude-mode
|
||||
"Toggle Claude mode.
|
||||
With a prefix argument ARG, enable Claude mode if ARG is positive,
|
||||
and disable it otherwise. If called from Lisp, enable the mode
|
||||
if ARG is omitted or nil.
|
||||
|
||||
Claude mode provides key bindings for interacting with the Claude AI assistant."
|
||||
:init-value nil
|
||||
:lighter " Claude"
|
||||
:keymap claude-mode-map
|
||||
:global t
|
||||
(if claude-mode
|
||||
(progn
|
||||
(message "Claude mode enabled")
|
||||
(claude-init-tools))
|
||||
(message "Claude mode disabled")))
|
||||
|
||||
;; Initialize tools on load
|
||||
(claude-init-tools)
|
||||
|
||||
(provide 'claude)
|
||||
;;; claude.el ends here
|
||||
Loading…
Add table
Add a link
Reference in a new issue