From 3ac73bb3e2268f8700d6bb1b96a1aa5a335a302f Mon Sep 17 00:00:00 2001 From: John Doty Date: Tue, 11 Mar 2025 11:53:37 -0700 Subject: [PATCH] [emacs] Claude integration? Written by Claude itself, mostly! --- .emacs.d/init.el | 53 +++- site-lisp/claude.el | 688 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 728 insertions(+), 13 deletions(-) create mode 100644 site-lisp/claude.el diff --git a/.emacs.d/init.el b/.emacs.d/init.el index f62f2d0..892dbb8 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -1380,25 +1380,52 @@ Do this when you edit your project view." ;; ================================================================= (use-package jsonnet-mode :ensure t) - -;; ================================================================= -;; Copilot (ugh) -;; ================================================================= -(use-package editorconfig :ensure) -(use-package jsonrpc :ensure) -;; (use-package copilot -;; :load-path (lambda () (expand-file-name "~/site-lisp/copilot")) -;; :after (editorconfig jsonrpc)) - ;; ================================================================= ;; Fish shell ;; ================================================================= (use-package fish-mode :ensure) ;; ================================================================= -;; gptel +;; AI Shit ;; ================================================================= -(use-package gptel :ensure - :bind ("C-c RET" . gptel-send)) +(use-package request :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 + ;; If you want to add any custom tools, add them here + ;; (claude-register-tool + ;; '(:name "my_custom_tool" + ;; :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 diff --git a/site-lisp/claude.el b/site-lisp/claude.el new file mode 100644 index 0000000..b0d0ecc --- /dev/null +++ b/site-lisp/claude.el @@ -0,0 +1,688 @@ +;;; claude.el --- Integration with Claude AI assistant -*- lexical-binding: t -*- + +;; Author: John Doty ; 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