Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2024 Petr "Pasky" Baudis
Copyright (c) 2024 Brian Panahi Johnson "bpanahij"

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ This vim plugin integrates Claude deeply into your Vim workflow - rather than
working in the clunky web Claude Chat, actually chat about and hack together
on your currently opened vim buffers.

**Claude is your pair programmer.** You chat about what to build or how
**Claude is your pair programmer.** You chat about what to build or how
to debug problems, and Claude offers opinions while seeing your actual code,
or goes ahead and proposes the modifications - high level, or just straight
writes the code.

This plugin is NOT:
* "code completion" like Github Copilot or Codeium.

- "code completion" like Github Copilot or Codeium.
(You can use these together with claude.vim!)
This plugin rather provides a chat / instruction centric interface.
* CLI coding framework. It is much more optimized for human collaboration than e.g. aider or dravid.
- CLI coding framework. It is much more optimized for human collaboration than e.g. aider or dravid.
You may want to agree on design decisions before Claude writes code.
And it is going to need feedback and change review in order to be helpful.
This is why the access to chat history and the vimdiff interface are the killer features.
Expand Down Expand Up @@ -53,17 +54,17 @@ And ultimately, Claude.vim can act as pretty much a full text terminal replaceme

![Claude is asked for a simple medical advice. It searches the web for the germ, then summarizes the results.](https://pbs.twimg.com/media/GTWwKLPWIAAEPub?format=jpg&name=medium)

----
---

Sonnet 3.5 is not yet good enough to completely autonomously perform complex tasks.
This is why you can chat with it, review and reject its changes and tool execution attempts, etc.
You still do the "hard thinking" and decide and tell it *what* to do.
You still do the "hard thinking" and decide and tell it _what_ to do.

That said, about 95% of the code of this plugin has been written by Claude
Sonnet 3.5, and most of the time already "self-hosted" within the plugin.

**NOTE: This is early alpha software.** It is expected to rapidly evolve...
and not just in backwards compatible way. Stay in touch with the maintainer
**NOTE: This is early alpha software.** It is expected to rapidly evolve...
and not just in backwards compatible way. Stay in touch with the maintainer
if you are using it (`pasky` on libera IRC, or @xpasky on Twitter / X, or just
via github issues or PRs).

Expand Down Expand Up @@ -108,17 +109,31 @@ Set your Claude API key in your .vimrc:
let g:claude_api_key = 'your_api_key_here'
```

Or (recommended) you can store your API key in 1Password. After installing 1Password's cli tool, you can also add the following to your Lazy config, which will tell the claude plugin to pull your Claude API key from you 1Password store. This is more secure than storing your API key in a config file directly.

```
return {
"pasky/claude.vim",
config = function()
vim.g.claude_api_key_command = "op read op://personal/Claude/credential --no-newline"
end,
}

```

(You can also use AWS Bedrock as your Claude provider instead - in that case, set `let g:claude_use_bedrock = 1` instead.)

## Usage

First, a couple of vim concepts you should be roughly familiar with:

- Switching between windows (`:help windows`) - at least `<C-W><C-W>` to cycle between active windows
- Diff mode (`:help diff`) - at least `d` `o` to accept the change under cursor
- Folds (`:help folding`) - at least `z` `o` to open a fold (chat interaction) and `z` `c` to close it
- Leader (`:help leader`) - if you are unsure, most likely `\` is the key to press whenever `<Leader>` is mentioned (but on new keyboards, `§` or `±` might be a nice leader to set)

Claude.vim currently offers two main interaction modes:

1. Simple implementation assistant
2. Chat interface

Expand All @@ -137,7 +152,7 @@ way; Claude proposes the change and lets you review and accept it.

### ClaudeChat

In this mode, you chat with Claude. You can chat about anything, really,
In this mode, you chat with Claude. You can chat about anything, really,
but the twist is that Claude also sees the full content of all your buffers
(listed in `:buffers` - _roughly_ any files you currently have open in your vim).

Expand All @@ -162,10 +177,10 @@ to propose implementation of even fairly complex new functionality. For example:

Previous interactions are automatically folded for easy orientation (Claude can
be a tad bit verbose), but the chat history is also visible to Claude when
asking it something. However, you can simply edit the buffer to arbitrarily
asking it something. However, you can simply edit the buffer to arbitrarily
redact the history (or just delete it).

**NOTE: For every single Claude Q&A roundtrip, full chat history and full
content of all buffers is sent. This can consume tokens FAST. (Even if it
content of all buffers is sent. This can consume tokens FAST. (Even if it
is not too expensive, remember that Claude also imposes a total daily token
limit.) Prune your chat history regularly.**
69 changes: 68 additions & 1 deletion plugin/claude.vim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ if !exists('g:claude_api_key')
let g:claude_api_key = ''
endif

if !exists('g:claude_api_key_command')
let g:claude_api_key_command = ''
endif

if !exists('g:claude_api_url')
let g:claude_api_url = 'https://api.anthropic.com/v1/messages'
endif
Expand All @@ -30,6 +34,64 @@ if !exists('g:claude_aws_profile')
let g:claude_aws_profile = ''
endif


function! claude#setup(config)
if has_key(a:config, 'api_key')
let g:claude_api_key = a:config.api_key
endif

if has_key(a:config, 'api_key_command')
let g:claude_api_key_command = a:config.api_key_command
endif

if has_key(a:config, 'api_url')
let g:claude_api_url = a:config.api_url
endif

if has_key(a:config, 'model')
let g:claude_model = a:config.model
endif

" ... [other configuration options] ...
endfunction

function! claude#setup_from_lua(config_string)
let l:config = json_decode(a:config_string)
call claude#setup(l:config)
endfunction

" Function to retrieve API key
let s:cached_claude_api_key = ''

function! s:GetClaudeAPIKey()
if !empty(s:cached_claude_api_key)
return s:cached_claude_api_key
endif

if !empty(g:claude_api_key)
let s:cached_claude_api_key = g:claude_api_key
elseif !empty(g:claude_api_key_command)
let l:api_key = system(g:claude_api_key_command)
let s:cached_claude_api_key = substitute(l:api_key, '\n\+$', '', '') " Remove trailing newlines
else
echoerr "Claude API key not set. Please set g:claude_api_key or g:claude_api_key_command."
return ''
endif

return s:cached_claude_api_key
endfunction


function! claude#setup(...)
let l:config = a:0 > 0 ? a:1 : {}
if has_key(l:config, 'api_key_command')
let g:claude_api_key_command = l:config.api_key_command
endif


" ... [add other configuration options as needed] ...
endfunction

"""""""""""""""""""""""""""""""""""""

let s:plugin_dir = expand('<sfile>:p:h')
Expand Down Expand Up @@ -60,6 +122,11 @@ function! s:ClaudeQueryInternal(messages, system_prompt, tools, stream_callback,
let l:headers = []
let l:url = ''

let l:api_key = s:GetClaudeAPIKey()
if empty(l:api_key)
return
endif

if g:claude_use_bedrock
let l:python_script = s:plugin_dir . '/claude_bedrock_helper.py'
let l:cmd = ['python3', l:python_script,
Expand Down Expand Up @@ -90,7 +157,7 @@ function! s:ClaudeQueryInternal(messages, system_prompt, tools, stream_callback,
let l:data['tools'] = a:tools
endif
call extend(l:headers, ['-H', 'Content-Type: application/json'])
call extend(l:headers, ['-H', 'x-api-key: ' . g:claude_api_key])
call extend(l:headers, ['-H', 'x-api-key: ' . l:api_key])
call extend(l:headers, ['-H', 'anthropic-version: 2023-06-01'])

" Convert data to JSON
Expand Down