From 94d67bbbdee46ad98a9de641ca648ddbe2b9adc7 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Sat, 26 Jul 2025 00:43:49 +0300 Subject: [PATCH 1/3] feat: add proxy support for Claude connections - Add proxy configuration settings (enabled, url, noProxy) - Pass proxy environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) to Claude process - Support proxy in both native and WSL modes - Apply proxy settings to terminal commands (/model, /api, /login) - Bump version to 1.0.5 --- package-lock.json | 4 +- package.json | 17 +++++++- src/extension.ts | 106 ++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82a87fc..369ea76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.0.5", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/mocha": "^10.0.10", diff --git a/package.json b/package.json index 92b42c5..e8e77d8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-code-chat", "displayName": "Claude Code Chat", "description": "Beautiful Claude Code Chat Interface for VS Code", - "version": "1.0.4", + "version": "1.0.5", "publisher": "AndrePimenta", "author": "Andre Pimenta", "repository": { @@ -185,6 +185,21 @@ "type": "boolean", "default": false, "description": "Enable Yolo Mode to skip all permission checks. Use with caution as Claude can execute any command without asking." + }, + "claudeCodeChat.proxy.enabled": { + "type": "boolean", + "default": false, + "description": "Enable proxy for Claude connections" + }, + "claudeCodeChat.proxy.url": { + "type": "string", + "default": "", + "description": "Proxy URL (e.g., http://proxy.example.com:8080) - will be used for both HTTP_PROXY and HTTPS_PROXY" + }, + "claudeCodeChat.proxy.noProxy": { + "type": "string", + "default": "", + "description": "Comma-separated list of hosts to bypass proxy (optional)" } } } diff --git a/src/extension.ts b/src/extension.ts index 4819670..d35d6d9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -448,6 +448,9 @@ class ClaudeChatProvider { // Get configuration const config = vscode.workspace.getConfiguration('claudeCodeChat'); const yoloMode = config.get('permissions.yoloMode', false); + const proxyEnabled = config.get('proxy.enabled', false); + const proxyUrl = config.get('proxy.url', ''); + const noProxy = config.get('proxy.noProxy', ''); if (yoloMode) { // Yolo mode: skip all permissions regardless of MCP config @@ -483,6 +486,27 @@ class ClaudeChatProvider { let claudeProcess: cp.ChildProcess; + // Prepare environment with proxy settings if enabled + const baseEnv: Record = { + ...process.env, + FORCE_COLOR: '0', + NO_COLOR: '1' + }; + + if (proxyEnabled && proxyUrl) { + baseEnv.HTTP_PROXY = proxyUrl; + baseEnv.HTTPS_PROXY = proxyUrl; + baseEnv.http_proxy = proxyUrl; // Some tools check lowercase + baseEnv.https_proxy = proxyUrl; + + if (noProxy) { + baseEnv.NO_PROXY = noProxy; + baseEnv.no_proxy = noProxy; + } + + console.log('Proxy configuration enabled:', { proxyUrl, noProxy: noProxy || 'none' }); + } + if (wslEnabled) { // Use WSL with bash -ic for proper environment loading console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath }); @@ -491,11 +515,7 @@ class ClaudeChatProvider { claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], { cwd: cwd, stdio: ['pipe', 'pipe', 'pipe'], - env: { - ...process.env, - FORCE_COLOR: '0', - NO_COLOR: '1' - } + env: baseEnv }); } else { // Use native claude command @@ -504,11 +524,7 @@ class ClaudeChatProvider { shell: process.platform === 'win32', cwd: cwd, stdio: ['pipe', 'pipe', 'pipe'], - env: { - ...process.env, - FORCE_COLOR: '0', - NO_COLOR: '1' - } + env: baseEnv }); } @@ -854,13 +870,30 @@ class ClaudeChatProvider { const wslDistro = config.get('wsl.distro', 'Ubuntu'); const nodePath = config.get('wsl.nodePath', '/usr/bin/node'); const claudePath = config.get('wsl.claudePath', '/usr/local/bin/claude'); + const proxyEnabled = config.get('proxy.enabled', false); + const proxyUrl = config.get('proxy.url', ''); + const noProxy = config.get('proxy.noProxy', ''); + + // Build proxy export commands if needed + let proxyExports = ''; + if (proxyEnabled && proxyUrl) { + proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; + if (noProxy) { + proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; + } + proxyExports += ' && '; + } // Open terminal and run claude login const terminal = vscode.window.createTerminal('Claude Login'); if (wslEnabled) { - terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath}`); + terminal.sendText(`wsl -d ${wslDistro} bash -c "${proxyExports}${nodePath} --no-warnings --enable-source-maps ${claudePath}"`); } else { - terminal.sendText('claude'); + if (proxyExports) { + terminal.sendText(`${proxyExports}claude`); + } else { + terminal.sendText('claude'); + } } terminal.show(); @@ -2033,7 +2066,10 @@ class ClaudeChatProvider { 'wsl.distro': config.get('wsl.distro', 'Ubuntu'), 'wsl.nodePath': config.get('wsl.nodePath', '/usr/bin/node'), 'wsl.claudePath': config.get('wsl.claudePath', '/usr/local/bin/claude'), - 'permissions.yoloMode': config.get('permissions.yoloMode', false) + 'permissions.yoloMode': config.get('permissions.yoloMode', false), + 'proxy.enabled': config.get('proxy.enabled', false), + 'proxy.url': config.get('proxy.url', ''), + 'proxy.noProxy': config.get('proxy.noProxy', '') }; this._postMessage({ @@ -2117,6 +2153,9 @@ class ClaudeChatProvider { const wslDistro = config.get('wsl.distro', 'Ubuntu'); const nodePath = config.get('wsl.nodePath', '/usr/bin/node'); const claudePath = config.get('wsl.claudePath', '/usr/local/bin/claude'); + const proxyEnabled = config.get('proxy.enabled', false); + const proxyUrl = config.get('proxy.url', ''); + const noProxy = config.get('proxy.noProxy', ''); // Build command arguments const args = ['/model']; @@ -2126,12 +2165,27 @@ class ClaudeChatProvider { args.push('--resume', this._currentSessionId); } + // Build proxy export commands if needed + let proxyExports = ''; + if (proxyEnabled && proxyUrl) { + proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; + if (noProxy) { + proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; + } + proxyExports += ' && '; + } + // Create terminal with the claude /model command const terminal = vscode.window.createTerminal('Claude Model Selection'); if (wslEnabled) { - terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`); + terminal.sendText(`wsl -d ${wslDistro} bash -c "${proxyExports}${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}"`); } else { - terminal.sendText(`claude ${args.join(' ')}`); + if (proxyExports) { + // For native, we need to set env vars before running claude + terminal.sendText(`${proxyExports}claude ${args.join(' ')}`); + } else { + terminal.sendText(`claude ${args.join(' ')}`); + } } terminal.show(); @@ -2154,6 +2208,9 @@ class ClaudeChatProvider { const wslDistro = config.get('wsl.distro', 'Ubuntu'); const nodePath = config.get('wsl.nodePath', '/usr/bin/node'); const claudePath = config.get('wsl.claudePath', '/usr/local/bin/claude'); + const proxyEnabled = config.get('proxy.enabled', false); + const proxyUrl = config.get('proxy.url', ''); + const noProxy = config.get('proxy.noProxy', ''); // Build command arguments const args = [`/${command}`]; @@ -2163,12 +2220,27 @@ class ClaudeChatProvider { args.push('--resume', this._currentSessionId); } + // Build proxy export commands if needed + let proxyExports = ''; + if (proxyEnabled && proxyUrl) { + proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; + if (noProxy) { + proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; + } + proxyExports += ' && '; + } + // Create terminal with the claude command const terminal = vscode.window.createTerminal(`Claude /${command}`); if (wslEnabled) { - terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`); + terminal.sendText(`wsl -d ${wslDistro} bash -c "${proxyExports}${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}"`); } else { - terminal.sendText(`claude ${args.join(' ')}`); + if (proxyExports) { + // For native, we need to set env vars before running claude + terminal.sendText(`${proxyExports}claude ${args.join(' ')}`); + } else { + terminal.sendText(`claude ${args.join(' ')}`); + } } terminal.show(); From d698d0c4b796f95f6013dfe7b3d7d5ad4fcac04c Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Sat, 26 Jul 2025 16:54:19 +0300 Subject: [PATCH 2/3] fixes after review from copilot --- package-lock.json | 4 +-- package.json | 2 +- src/extension.ts | 66 +++++++++++++++++++++++------------------------ src/ui-styles.ts | 4 +-- src/ui.ts | 2 +- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 369ea76..82a87fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-chat", - "version": "1.0.5", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-chat", - "version": "1.0.5", + "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/mocha": "^10.0.10", diff --git a/package.json b/package.json index e8e77d8..bf59df4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-code-chat", "displayName": "Claude Code Chat", "description": "Beautiful Claude Code Chat Interface for VS Code", - "version": "1.0.5", + "version": "1.0.4", "publisher": "AndrePimenta", "author": "Andre Pimenta", "repository": { diff --git a/src/extension.ts b/src/extension.ts index d35d6d9..9cbd85c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -378,6 +378,27 @@ class ClaudeChatProvider { } } + private _escapeShellArg(arg: string): string { + // Escape shell metacharacters to prevent injection + return arg.replace(/([`$"\\])/g, '\\$1'); + } + + private _buildProxyExports(proxyEnabled: boolean, proxyUrl: string, noProxy: string): string { + if (!proxyEnabled || !proxyUrl) { + return ''; + } + + const escapedProxyUrl = this._escapeShellArg(proxyUrl); + let proxyExports = `export HTTP_PROXY="${escapedProxyUrl}" && export HTTPS_PROXY="${escapedProxyUrl}" && export http_proxy="${escapedProxyUrl}" && export https_proxy="${escapedProxyUrl}"`; + + if (noProxy) { + const escapedNoProxy = this._escapeShellArg(noProxy); + proxyExports += ` && export NO_PROXY="${escapedNoProxy}" && export no_proxy="${escapedNoProxy}"`; + } + + return proxyExports + ' && '; + } + private async _sendMessageToClaude(message: string, planMode?: boolean, thinkingMode?: boolean) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const cwd = workspaceFolder ? workspaceFolder.uri.fsPath : process.cwd(); @@ -393,7 +414,7 @@ class ClaudeChatProvider { } if (thinkingMode) { let thinkingPrompt = ''; - const thinkingMesssage = ' THROUGH THIS STEP BY STEP: \n' + const thinkingMesssage = ' THROUGH THIS STEP BY STEP: \n'; switch (thinkingIntensity) { case 'think': thinkingPrompt = 'THINK'; @@ -504,7 +525,7 @@ class ClaudeChatProvider { baseEnv.no_proxy = noProxy; } - console.log('Proxy configuration enabled:', { proxyUrl, noProxy: noProxy || 'none' }); + console.log('Proxy configuration enabled'); } if (wslEnabled) { @@ -707,7 +728,7 @@ class ClaudeChatProvider { const isError = content.is_error || false; // Find the last tool use to get the tool name - const lastToolUse = this._currentConversation[this._currentConversation.length-1] + const lastToolUse = this._currentConversation[this._currentConversation.length-1]; const toolName = lastToolUse?.data?.toolName; @@ -875,14 +896,7 @@ class ClaudeChatProvider { const noProxy = config.get('proxy.noProxy', ''); // Build proxy export commands if needed - let proxyExports = ''; - if (proxyEnabled && proxyUrl) { - proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; - if (noProxy) { - proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; - } - proxyExports += ' && '; - } + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); // Open terminal and run claude login const terminal = vscode.window.createTerminal('Claude Login'); @@ -1148,7 +1162,7 @@ class ClaudeChatProvider { console.log(`Created permission requests directory at: ${this._permissionRequestsPath}`); } - console.log("DIRECTORY-----", this._permissionRequestsPath) + console.log("DIRECTORY-----", this._permissionRequestsPath); // Set up file watcher for *.request files this._permissionWatcher = vscode.workspace.createFileSystemWatcher( @@ -1156,7 +1170,7 @@ class ClaudeChatProvider { ); this._permissionWatcher.onDidCreate(async (uri) => { - console.log("----file", uri) + console.log("----file", uri); // Only handle file scheme URIs, ignore vscode-userdata scheme if (uri.scheme === 'file') { await this._handlePermissionRequest(uri); @@ -1245,7 +1259,7 @@ class ClaudeChatProvider { try { // Read the original request to get tool name and input const storagePath = this._context.storageUri?.fsPath; - if (!storagePath) return; + if (!storagePath) {return;} const requestFileUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', `${requestId}.request`)); @@ -1308,7 +1322,7 @@ class ClaudeChatProvider { private getCommandPattern(command: string): string { const parts = command.trim().split(/\s+/); - if (parts.length === 0) return command; + if (parts.length === 0) {return command;} const baseCmd = parts[0]; const subCmd = parts.length > 1 ? parts[1] : ''; @@ -1437,7 +1451,7 @@ class ClaudeChatProvider { private async _removePermission(toolName: string, command: string | null): Promise { try { const storagePath = this._context.storageUri?.fsPath; - if (!storagePath) return; + if (!storagePath) {return;} const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); let permissions: any = { alwaysAllow: {} }; @@ -1483,7 +1497,7 @@ class ClaudeChatProvider { private async _addPermission(toolName: string, command: string | null): Promise { try { const storagePath = this._context.storageUri?.fsPath; - if (!storagePath) return; + if (!storagePath) {return;} const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); let permissions: any = { alwaysAllow: {} }; @@ -2166,14 +2180,7 @@ class ClaudeChatProvider { } // Build proxy export commands if needed - let proxyExports = ''; - if (proxyEnabled && proxyUrl) { - proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; - if (noProxy) { - proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; - } - proxyExports += ' && '; - } + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); // Create terminal with the claude /model command const terminal = vscode.window.createTerminal('Claude Model Selection'); @@ -2221,14 +2228,7 @@ class ClaudeChatProvider { } // Build proxy export commands if needed - let proxyExports = ''; - if (proxyEnabled && proxyUrl) { - proxyExports = `export HTTP_PROXY="${proxyUrl}" && export HTTPS_PROXY="${proxyUrl}" && export http_proxy="${proxyUrl}" && export https_proxy="${proxyUrl}"`; - if (noProxy) { - proxyExports += ` && export NO_PROXY="${noProxy}" && export no_proxy="${noProxy}"`; - } - proxyExports += ' && '; - } + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); // Create terminal with the claude command const terminal = vscode.window.createTerminal(`Claude /${command}`); diff --git a/src/ui-styles.ts b/src/ui-styles.ts index 6340c50..c94d97e 100644 --- a/src/ui-styles.ts +++ b/src/ui-styles.ts @@ -2874,6 +2874,6 @@ const styles = ` overflow: hidden; text-overflow: ellipsis; } -` +`; -export default styles \ No newline at end of file +export default styles; \ No newline at end of file diff --git a/src/ui.ts b/src/ui.ts index d1d7d48..0a2ea8c 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,4 +1,4 @@ -import styles from './ui-styles' +import styles from './ui-styles'; const html = ` From c3fdf31deddcde2480c6fe1bf078a389fd31db6c Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Sat, 26 Jul 2025 17:15:08 +0300 Subject: [PATCH 3/3] updated _escapeShellArg --- src/extension.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 9cbd85c..8a464f1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -380,7 +380,10 @@ class ClaudeChatProvider { private _escapeShellArg(arg: string): string { // Escape shell metacharacters to prevent injection - return arg.replace(/([`$"\\])/g, '\\$1'); + // Includes: backtick, dollar, quotes, backslash, semicolon, pipe, ampersand, + // redirects, parentheses, braces, brackets, single quote, exclamation, + // hash, tilde, asterisk, question mark, whitespace (space, tab, newline) + return arg.replace(/([`$"\\;|&><(){}\[\]'!#~*?\s])/g, '\\$1'); } private _buildProxyExports(proxyEnabled: boolean, proxyUrl: string, noProxy: string): string {