@@ -72,6 +72,11 @@ local function is_anthropic()
7272 return config .provider == " anthropic"
7373end
7474
75+ local function is_claude_code ()
76+ local config = get_config ()
77+ return config .provider == " claude_code"
78+ end
79+
7580local function build_curl_cmd (body_json , config , extra_headers )
7681 local cmd = { " curl" , " -s" , " -H" , " Content-Type: application/json" }
7782 for _ , h in ipairs (extra_headers or {}) do
@@ -113,9 +118,168 @@ local function extract_content_anthropic(parsed)
113118 return nil
114119end
115120
121+ local function extract_content_claude_code (parsed )
122+ return parsed .result
123+ end
124+
125+ local function claude_code_env ()
126+ local env = vim .fn .environ ()
127+ -- Claude Code prefers an API key over its subscription login if both exist.
128+ env .ANTHROPIC_API_KEY = nil
129+ return env
130+ end
131+
132+ function M ._build_claude_code_cmd (messages , config )
133+ local system_text = " "
134+ local user_text = " "
135+ for _ , message in ipairs (messages ) do
136+ if message .role == " system" then
137+ system_text = message .content
138+ elseif message .role == " user" then
139+ user_text = message .content
140+ end
141+ end
142+
143+ local cmd = {
144+ config .claude_code_command or " claude" ,
145+ " -p" ,
146+ " --output-format" ,
147+ " json" ,
148+ " --tools" ,
149+ " " ,
150+ " --bare" ,
151+ " --strict-mcp-config" ,
152+ " --no-session-persistence" ,
153+ " --system-prompt" ,
154+ system_text ,
155+ }
156+ if config .model and config .model ~= " " then
157+ table.insert (cmd , " --model" )
158+ table.insert (cmd , config .model )
159+ end
160+ table.insert (cmd , user_text )
161+ return cmd
162+ end
163+
164+ local function make_claude_code_request (messages , callback )
165+ local config = get_config ()
166+ local command = config .claude_code_command or " claude"
167+ local loading = require (" jumpy.loading" )
168+
169+ if vim .fn .executable (command ) ~= 1 then
170+ loading .error (" Claude Code CLI not found; install it or set claude_code_command" )
171+ return
172+ end
173+
174+ local env = claude_code_env ()
175+ local cancelled = false
176+ loading .start ()
177+
178+ local auth_jid
179+ auth_jid = vim .fn .jobstart ({ command , " auth" , " status" }, {
180+ env = env ,
181+ clear_env = true ,
182+ stdout_buffered = true ,
183+ stderr_buffered = true ,
184+ on_exit = function (_ , exit_code )
185+ if not loading .is_active () then
186+ cancelled = true
187+ end
188+ if cancelled then
189+ loading .stop ()
190+ return
191+ end
192+ if exit_code ~= 0 then
193+ vim .schedule (function ()
194+ loading .error (" Claude Code is not authenticated; run 'claude login' and select your Claude plan" )
195+ end )
196+ return
197+ end
198+
199+ local response_chunks = {}
200+ local stderr_chunks = {}
201+ local request_jid
202+ request_jid = vim .fn .jobstart (M ._build_claude_code_cmd (messages , config ), {
203+ env = env ,
204+ clear_env = true ,
205+ stdout_buffered = true ,
206+ stderr_buffered = true ,
207+ on_stdout = function (_ , data )
208+ if data then
209+ for _ , line in ipairs (data ) do
210+ table.insert (response_chunks , line )
211+ end
212+ end
213+ end ,
214+ on_stderr = function (_ , data )
215+ if data then
216+ for _ , line in ipairs (data ) do
217+ if line ~= " " then
218+ table.insert (stderr_chunks , line )
219+ end
220+ end
221+ end
222+ end ,
223+ on_exit = function (_ , request_exit_code )
224+ if not loading .is_active () then
225+ cancelled = true
226+ end
227+ if cancelled then
228+ loading .stop ()
229+ return
230+ end
231+
232+ local stderr_text = table.concat (stderr_chunks , " \n " )
233+ if request_exit_code ~= 0 then
234+ vim .schedule (function ()
235+ local msg = " Claude Code request failed (exit " .. request_exit_code .. " )"
236+ if stderr_text ~= " " then
237+ msg = msg .. " — " .. stderr_text
238+ end
239+ loading .error (msg )
240+ end )
241+ return
242+ end
243+
244+ local raw = table.concat (response_chunks , " \n " )
245+ local ok , parsed = pcall (vim .fn .json_decode , raw )
246+ local content = ok and extract_content_claude_code (parsed ) or nil
247+ if type (content ) ~= " string" or content == " " then
248+ vim .schedule (function ()
249+ loading .error (" Claude Code returned an invalid response (update the CLI and try again)" )
250+ end )
251+ return
252+ end
253+
254+ loading .stop ()
255+ content = content :gsub (" ^```[%w]*\n " , " " ):gsub (" \n ```%s*$" , " " )
256+ callback (content )
257+ end ,
258+ })
259+
260+ if request_jid <= 0 then
261+ loading .error (" failed to start Claude Code CLI" )
262+ else
263+ loading .set_job (request_jid )
264+ end
265+ end ,
266+ })
267+
268+ if auth_jid <= 0 then
269+ loading .error (" failed to start Claude Code CLI" )
270+ else
271+ loading .set_job (auth_jid )
272+ end
273+ end
274+
116275local function make_request (messages , callback )
117276 local config = get_config ()
118277
278+ if is_claude_code () then
279+ make_claude_code_request (messages , callback )
280+ return
281+ end
282+
119283 if not config .api_key or config .api_key == " " then
120284 local loading = require (" jumpy.loading" )
121285 loading .error (" no API key — set " .. (config .provider or " JUMPY" ) .. " env var or pass api_key in setup()" )
0 commit comments