diff --git a/package.json b/package.json index 92b42c5..bf59df4 100644 --- a/package.json +++ b/package.json @@ -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..8a464f1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -378,6 +378,30 @@ class ClaudeChatProvider { } } + private _escapeShellArg(arg: string): string { + // Escape shell metacharacters to prevent injection + // 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 { + 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 +417,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'; @@ -448,6 +472,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 +510,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'); + } + if (wslEnabled) { // Use WSL with bash -ic for proper environment loading console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath }); @@ -491,11 +539,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 +548,7 @@ class ClaudeChatProvider { shell: process.platform === 'win32', cwd: cwd, stdio: ['pipe', 'pipe', 'pipe'], - env: { - ...process.env, - FORCE_COLOR: '0', - NO_COLOR: '1' - } + env: baseEnv }); } @@ -691,7 +731,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; @@ -854,13 +894,23 @@ 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 + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); // 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(); @@ -1115,7 +1165,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( @@ -1123,7 +1173,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); @@ -1212,7 +1262,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`)); @@ -1275,7 +1325,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] : ''; @@ -1404,7 +1454,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: {} }; @@ -1450,7 +1500,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: {} }; @@ -2033,7 +2083,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 +2170,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 +2182,20 @@ class ClaudeChatProvider { args.push('--resume', this._currentSessionId); } + // Build proxy export commands if needed + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); + // 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 +2218,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 +2230,20 @@ class ClaudeChatProvider { args.push('--resume', this._currentSessionId); } + // Build proxy export commands if needed + const proxyExports = this._buildProxyExports(proxyEnabled, proxyUrl, noProxy); + // 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(); 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 = `