From f4d8aceeb613350814327d18567cac3fd0db960c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kaln=C3=BD?= Date: Sat, 7 Jun 2025 14:50:37 +0200 Subject: [PATCH 1/3] feat: add env configuration option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add env field to config for passing environment variables to Claude CLI - Update init.lua to pass env variables when spawning Claude terminal - Allows users to set custom environment like ANTHROPIC_API_KEY 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lua/claudecode/config.lua | 16 ++++++++++++++++ lua/claudecode/init.lua | 6 ++++-- lua/claudecode/terminal.lua | 19 ++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lua/claudecode/config.lua b/lua/claudecode/config.lua index 573fc4c..faeca11 100644 --- a/lua/claudecode/config.lua +++ b/lua/claudecode/config.lua @@ -6,6 +6,7 @@ M.defaults = { port_range = { min = 10000, max = 65535 }, auto_start = true, terminal_cmd = nil, + env = {}, -- Custom environment variables for Claude terminal log_level = "info", track_selection = true, visual_demotion_delay_ms = 50, -- Milliseconds to wait before demoting a visual selection @@ -74,6 +75,13 @@ function M.validate(config) assert(type(config.diff_opts.vertical_split) == "boolean", "diff_opts.vertical_split must be a boolean") assert(type(config.diff_opts.open_in_current_tab) == "boolean", "diff_opts.open_in_current_tab must be a boolean") + -- Validate env + assert(type(config.env) == "table", "env must be a table") + for key, value in pairs(config.env) do + assert(type(key) == "string", "env keys must be strings") + assert(type(value) == "string", "env values must be strings") + end + return true end @@ -87,6 +95,14 @@ function M.apply(user_config) config = vim.tbl_deep_extend("force", config, user_config) end + -- Check environment variable for terminal_cmd if not set in user config + if config.terminal_cmd == nil then + local env_cmd = vim.fn.getenv("CLAUDE_TERMINAL_CMD") + if env_cmd ~= vim.NIL then + config.terminal_cmd = env_cmd + end + end + M.validate(config) return config diff --git a/lua/claudecode/init.lua b/lua/claudecode/init.lua index 9547ef6..73ad116 100644 --- a/lua/claudecode/init.lua +++ b/lua/claudecode/init.lua @@ -36,6 +36,7 @@ M.version = { --- @field port_range {min: integer, max: integer} Port range for WebSocket server. --- @field auto_start boolean Auto-start WebSocket server on Neovim startup. --- @field terminal_cmd string|nil Custom terminal command to use when launching Claude. +--- @field env table Custom environment variables for Claude terminal. --- @field log_level "trace"|"debug"|"info"|"warn"|"error" Log level. --- @field track_selection boolean Enable sending selection updates to Claude. --- @field visual_demotion_delay_ms number Milliseconds to wait before demoting a visual selection. @@ -49,6 +50,7 @@ local default_config = { port_range = { min = 10000, max = 65535 }, auto_start = true, terminal_cmd = nil, + env = {}, log_level = "info", track_selection = true, visual_demotion_delay_ms = 50, -- Reduced from 200ms for better responsiveness in tree navigation @@ -303,14 +305,14 @@ function M.setup(opts) logger.setup(M.state.config) - -- Setup terminal module: always try to call setup to pass terminal_cmd, + -- Setup terminal module: always try to call setup to pass terminal_cmd and env, -- even if terminal_opts (for split_side etc.) are not provided. local terminal_setup_ok, terminal_module = pcall(require, "claudecode.terminal") if terminal_setup_ok then -- Guard in case tests or user replace the module with a minimal stub without `setup`. if type(terminal_module.setup) == "function" then -- terminal_opts might be nil, which the setup function should handle gracefully. - terminal_module.setup(terminal_opts, M.state.config.terminal_cmd) + terminal_module.setup(terminal_opts, M.state.config.terminal_cmd, M.state.config.env) end else logger.error("init", "Failed to load claudecode.terminal module for setup.") diff --git a/lua/claudecode/terminal.lua b/lua/claudecode/terminal.lua index 896a5da..3409805 100644 --- a/lua/claudecode/terminal.lua +++ b/lua/claudecode/terminal.lua @@ -24,6 +24,7 @@ local config = { show_native_term_exit_tip = true, terminal_cmd = nil, auto_close = true, + env = {}, -- Custom environment variables for Claude terminal } -- Lazy load providers @@ -148,6 +149,11 @@ local function get_claude_command_and_env(cmd_args) env_table["CLAUDE_CODE_SSE_PORT"] = tostring(sse_port_value) end + -- Merge custom environment variables from config + for key, value in pairs(config.env) do + env_table[key] = value + end + return cmd_string, env_table end @@ -180,7 +186,8 @@ end -- @field user_term_config.provider string 'snacks' or 'native' (default: 'snacks'). -- @field user_term_config.show_native_term_exit_tip boolean Show tip for exiting native terminal (default: true). -- @param p_terminal_cmd string|nil The command to run in the terminal (from main config). -function M.setup(user_term_config, p_terminal_cmd) +-- @param p_env table|nil Custom environment variables to pass to the terminal (from main config). +function M.setup(user_term_config, p_terminal_cmd, p_env) if user_term_config == nil then -- Allow nil, default to empty table silently user_term_config = {} elseif type(user_term_config) ~= "table" then -- Warn if it's not nil AND not a table @@ -198,6 +205,16 @@ function M.setup(user_term_config, p_terminal_cmd) config.terminal_cmd = nil -- Fallback to default behavior end + if p_env == nil or type(p_env) == "table" then + config.env = p_env or {} + else + vim.notify( + "claudecode.terminal.setup: Invalid env provided: " .. tostring(p_env) .. ". Using empty table.", + vim.log.levels.WARN + ) + config.env = {} + end + for k, v in pairs(user_term_config) do if config[k] ~= nil and k ~= "terminal_cmd" then -- terminal_cmd is handled above if k == "split_side" and (v == "left" or v == "right") then From eb95e598fccb5c5168fec13ac64a63b128ab515d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kaln=C3=BD?= Date: Mon, 9 Jun 2025 10:07:17 +0200 Subject: [PATCH 2/3] chore: remove trailing newlines and teminal cmd env var --- lua/claudecode/config.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lua/claudecode/config.lua b/lua/claudecode/config.lua index faeca11..fd7c1f8 100644 --- a/lua/claudecode/config.lua +++ b/lua/claudecode/config.lua @@ -95,14 +95,6 @@ function M.apply(user_config) config = vim.tbl_deep_extend("force", config, user_config) end - -- Check environment variable for terminal_cmd if not set in user config - if config.terminal_cmd == nil then - local env_cmd = vim.fn.getenv("CLAUDE_TERMINAL_CMD") - if env_cmd ~= vim.NIL then - config.terminal_cmd = env_cmd - end - end - M.validate(config) return config From 606b259f4c823d99f46a0e5cf19e88b44bec6c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Kaln=C3=BD?= Date: Wed, 18 Jun 2025 08:31:09 +0200 Subject: [PATCH 3/3] fix: properly schedule vim.notify and nvim_echo calls --- lua/claudecode/logger.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lua/claudecode/logger.lua b/lua/claudecode/logger.lua index 44418a3..7246eec 100644 --- a/lua/claudecode/logger.lua +++ b/lua/claudecode/logger.lua @@ -68,18 +68,18 @@ local function log(level, component, message_parts) end end - if level == M.levels.ERROR then - vim.notify(prefix .. " " .. message, vim.log.levels.ERROR, { title = "ClaudeCode Error" }) - elseif level == M.levels.WARN then - vim.notify(prefix .. " " .. message, vim.log.levels.WARN, { title = "ClaudeCode Warning" }) - else - -- For INFO, DEBUG, TRACE, use nvim_echo to avoid flooding notifications, - -- to make them appear in :messages, and wrap in vim.schedule - -- to avoid "nvim_echo must not be called in a fast event context". - vim.schedule(function() + -- Wrap all vim.notify and nvim_echo calls in vim.schedule to avoid + -- "nvim_echo must not be called in a fast event context" errors + vim.schedule(function() + if level == M.levels.ERROR then + vim.notify(prefix .. " " .. message, vim.log.levels.ERROR, { title = "ClaudeCode Error" }) + elseif level == M.levels.WARN then + vim.notify(prefix .. " " .. message, vim.log.levels.WARN, { title = "ClaudeCode Warning" }) + else + -- For INFO, DEBUG, TRACE, use nvim_echo to avoid flooding notifications vim.api.nvim_echo({ { prefix .. " " .. message, "Normal" } }, true, {}) - end) - end + end + end) end --- @param component string|nil Optional component/module name.