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
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
}
}
Expand Down
125 changes: 100 additions & 25 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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';
Expand Down Expand Up @@ -448,6 +472,9 @@ class ClaudeChatProvider {
// Get configuration
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const yoloMode = config.get<boolean>('permissions.yoloMode', false);
const proxyEnabled = config.get<boolean>('proxy.enabled', false);
const proxyUrl = config.get<string>('proxy.url', '');
const noProxy = config.get<string>('proxy.noProxy', '');

if (yoloMode) {
// Yolo mode: skip all permissions regardless of MCP config
Expand Down Expand Up @@ -483,6 +510,27 @@ class ClaudeChatProvider {

let claudeProcess: cp.ChildProcess;

// Prepare environment with proxy settings if enabled
const baseEnv: Record<string, string> = {
...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 });
Expand All @@ -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
Expand All @@ -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
});
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -854,13 +894,23 @@ class ClaudeChatProvider {
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
const proxyEnabled = config.get<boolean>('proxy.enabled', false);
const proxyUrl = config.get<string>('proxy.url', '');
const noProxy = config.get<string>('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();

Expand Down Expand Up @@ -1115,15 +1165,15 @@ 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(
new vscode.RelativePattern(this._permissionRequestsPath, '*.request')
);

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);
Expand Down Expand Up @@ -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`));

Expand Down Expand Up @@ -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] : '';
Expand Down Expand Up @@ -1404,7 +1454,7 @@ class ClaudeChatProvider {
private async _removePermission(toolName: string, command: string | null): Promise<void> {
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: {} };
Expand Down Expand Up @@ -1450,7 +1500,7 @@ class ClaudeChatProvider {
private async _addPermission(toolName: string, command: string | null): Promise<void> {
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: {} };
Expand Down Expand Up @@ -2033,7 +2083,10 @@ class ClaudeChatProvider {
'wsl.distro': config.get<string>('wsl.distro', 'Ubuntu'),
'wsl.nodePath': config.get<string>('wsl.nodePath', '/usr/bin/node'),
'wsl.claudePath': config.get<string>('wsl.claudePath', '/usr/local/bin/claude'),
'permissions.yoloMode': config.get<boolean>('permissions.yoloMode', false)
'permissions.yoloMode': config.get<boolean>('permissions.yoloMode', false),
'proxy.enabled': config.get<boolean>('proxy.enabled', false),
'proxy.url': config.get<string>('proxy.url', ''),
'proxy.noProxy': config.get<string>('proxy.noProxy', '')
};

this._postMessage({
Expand Down Expand Up @@ -2117,6 +2170,9 @@ class ClaudeChatProvider {
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
const proxyEnabled = config.get<boolean>('proxy.enabled', false);
const proxyUrl = config.get<string>('proxy.url', '');
const noProxy = config.get<string>('proxy.noProxy', '');

// Build command arguments
const args = ['/model'];
Expand All @@ -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();

Expand All @@ -2154,6 +2218,9 @@ class ClaudeChatProvider {
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
const proxyEnabled = config.get<boolean>('proxy.enabled', false);
const proxyUrl = config.get<string>('proxy.url', '');
const noProxy = config.get<string>('proxy.noProxy', '');

// Build command arguments
const args = [`/${command}`];
Expand All @@ -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();

Expand Down
4 changes: 2 additions & 2 deletions src/ui-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2874,6 +2874,6 @@ const styles = `
overflow: hidden;
text-overflow: ellipsis;
}
</style>`
</style>`;

export default styles
export default styles;
2 changes: 1 addition & 1 deletion src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styles from './ui-styles'
import styles from './ui-styles';
const html = `<!DOCTYPE html>
<html lang="en">
<head>
Expand Down