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
93 changes: 93 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,99 @@ This instructs the AI to proceed without user input.
echo "Implement the new feature" | kilocode --ci --timeout 600
```

## Proxy Configuration

The CLI supports HTTP/HTTPS proxy configuration through environment variables. This is useful when running behind corporate proxies or when you need to route traffic through a proxy server.

### Supported Environment Variables

- `HTTP_PROXY` / `http_proxy`: Proxy URL for HTTP requests
- `HTTPS_PROXY` / `https_proxy`: Proxy URL for HTTPS requests
- `ALL_PROXY` / `all_proxy`: Fallback proxy for all protocols
- `NO_PROXY` / `no_proxy`: Comma-separated list of domains to bypass proxy
- `NODE_TLS_REJECT_UNAUTHORIZED`: Set to `0` to disable SSL certificate validation (use with caution)

### Proxy URL Format

```
http://[username:password@]proxy-host:port
```

### Examples

#### Basic Proxy Configuration

```bash
# Set proxy for HTTP and HTTPS
export HTTP_PROXY=http://localhost:8080
export HTTPS_PROXY=http://localhost:8080

# Run CLI
kilocode
```

#### Proxy with Authentication

```bash
# Proxy with username and password
export HTTPS_PROXY=http://username:password@proxy.company.com:8080

kilocode
```

#### Bypass Proxy for Specific Domains

```bash
# Set proxy
export HTTPS_PROXY=http://localhost:8080

# Bypass proxy for localhost and internal domains
export NO_PROXY=localhost,127.0.0.1,*.internal.company.com,192.168.0.0/16

kilocode
```

#### Self-Signed Certificates

```bash
# Disable SSL certificate validation (use with caution in development only)
export NODE_TLS_REJECT_UNAUTHORIZED=0
export HTTPS_PROXY=http://localhost:8080

kilocode
```

#### One-Line Command

```bash
# Run with proxy settings in a single command
HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080 NODE_TLS_REJECT_UNAUTHORIZED=0 kilocode
```

### NO_PROXY Patterns

The `NO_PROXY` environment variable supports various patterns:

- **Exact domain**: `example.com`
- **Wildcard subdomains**: `*.example.com`
- **IP addresses**: `192.168.1.1`
- **CIDR ranges**: `192.168.0.0/16`
- **Port-specific**: `example.com:8080`
- **Multiple patterns**: `localhost,127.0.0.1,*.internal.com`

### Troubleshooting

If proxy is not working:

1. **Check proxy logs**: The CLI will log proxy configuration on startup
2. **Verify proxy URL**: Ensure the proxy URL is correct and accessible
3. **Test proxy**: Use `curl` to test if the proxy is working:
```bash
curl -x http://localhost:8080 https://api.kilocode.ai
```
4. **Check NO_PROXY**: Ensure the target domain is not in NO_PROXY list
5. **Certificate issues**: If you see SSL errors, you may need to set `NODE_TLS_REJECT_UNAUTHORIZED=0` (development only)

## Local Development

### DevTools
Expand Down
2 changes: 2 additions & 0 deletions cli/esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ const __dirname = __dirname__(__filename);
"get-folder-size",
"google-auth-library",
"gray-matter",
"http-proxy-agent",
"https-proxy-agent",
"i18next",
"ignore",
"ink",
Expand Down
2 changes: 2 additions & 0 deletions cli/package.dist.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"get-folder-size": "^5.0.0",
"google-auth-library": "^9.15.1",
"gray-matter": "^4.0.3",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"i18next": "^25.0.0",
"ignore": "^7.0.3",
"ink": "^6.3.1",
Expand Down
2 changes: 2 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"get-folder-size": "^5.0.0",
"google-auth-library": "^9.15.1",
"gray-matter": "^4.0.3",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"i18next": "^25.0.0",
"ignore": "^7.0.3",
"ink": "^6.3.1",
Expand Down
15 changes: 15 additions & 0 deletions cli/src/host/ExtensionHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,21 @@ export class ExtensionHost extends EventEmitter {
const originalResolveFilename = ModuleClass._resolveFilename
const originalCompile = ModuleClass.prototype._compile

// Configure proxy BEFORE loading the extension
// This ensures the extension's HTTP clients get the proxy configuration
try {
// Load axios in the extension's require context to ensure it's the same instance
require("axios")
const { configureProxy } = await import("../utils/proxy-config.js")

// Apply proxy configuration to all HTTP clients that will be used by the extension
logs.debug("Configuring proxy for extension context", "ExtensionHost")
configureProxy()
logs.debug("Proxy configured for extension", "ExtensionHost")
} catch (error) {
logs.warn("Failed to configure proxy for extension", "ExtensionHost", { error })
}

// Set up module resolution interception for vscode
ModuleClass._resolveFilename = function (request: string, parent: any, isMain: boolean, options?: any) {
if (request === "vscode") {
Expand Down
4 changes: 4 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import { loadEnvFile } from "./utils/env-loader.js"
loadEnvFile()

// Configure proxy settings immediately after loading environment
import { configureProxy } from "./utils/proxy-config.js"
configureProxy()

import { Command } from "commander"
import { existsSync } from "fs"
import { spawn } from "child_process"
Expand Down
2 changes: 2 additions & 0 deletions cli/src/services/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export class ExtensionService extends EventEmitter {
})

// Setup proper message routing to avoid IPC timeouts
// This is the ONLY handler for TUI messages - removed duplicate tuiRequest handler
this.messageBridge.getTUIChannel().on("message", async (ipcMessage) => {
if (ipcMessage.type === "request") {
try {
Expand All @@ -172,6 +173,7 @@ export class ExtensionService extends EventEmitter {

/**
* Handle TUI messages and return response
* This is the single point of entry for all TUI->Extension messages
*/
private async handleTUIMessage(data: any): Promise<any> {
try {
Expand Down
174 changes: 174 additions & 0 deletions cli/src/utils/proxy-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Proxy configuration for CLI
* Reads proxy settings from environment variables and configures axios, fetch, and undici
*/

import axios from "axios"
import { HttpProxyAgent } from "http-proxy-agent"
import { HttpsProxyAgent } from "https-proxy-agent"
import { logs } from "../services/logs.js"
import { parseNoProxy, shouldBypassProxy } from "./proxy-matcher.js"

export interface ProxyConfig {
httpProxy?: string
httpsProxy?: string
noProxy: string[]
rejectUnauthorized: boolean
}

/**
* Get proxy configuration from environment variables
*/
export function getProxyConfig(): ProxyConfig {
// Read proxy environment variables (case-insensitive)
const httpProxy =
process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || undefined

const httpsProxy =
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.ALL_PROXY ||
process.env.all_proxy ||
undefined

const noProxyStr = process.env.NO_PROXY || process.env.no_proxy || ""
const noProxy = parseNoProxy(noProxyStr)

// Handle NODE_TLS_REJECT_UNAUTHORIZED for self-signed certificates
const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0"

const result: ProxyConfig = {
noProxy,
rejectUnauthorized,
}

if (httpProxy) {
result.httpProxy = httpProxy
}

if (httpsProxy) {
result.httpsProxy = httpsProxy
}

return result
}

/**
* Configure axios, fetch, and undici to use proxy settings from environment variables
* This must be called before any HTTP requests are made
*/
export function configureProxy(): void {
const config = getProxyConfig()

// Log proxy configuration (without credentials)
if (config.httpProxy || config.httpsProxy) {
logs.info("Configuring proxy settings:", "Proxy")
if (config.httpProxy) {
logs.info(` HTTP_PROXY: ${sanitizeProxyUrl(config.httpProxy)}`, "Proxy")
}
if (config.httpsProxy) {
logs.info(` HTTPS_PROXY: ${sanitizeProxyUrl(config.httpsProxy)}`, "Proxy")
}
if (config.noProxy.length > 0) {
logs.info(` NO_PROXY: ${config.noProxy.join(", ")}`, "Proxy")
}
if (!config.rejectUnauthorized) {
logs.warn(" TLS certificate validation: DISABLED (NODE_TLS_REJECT_UNAUTHORIZED=0)", "Proxy")
}
}

// Create proxy agents
const httpProxyAgent = config.httpProxy ? new HttpProxyAgent(config.httpProxy) : undefined
const httpsProxyAgent = config.httpsProxy
? new HttpsProxyAgent(config.httpsProxy, {
rejectUnauthorized: config.rejectUnauthorized,
})
: undefined

// Configure axios defaults
axios.defaults.httpAgent = httpProxyAgent
axios.defaults.httpsAgent = httpsProxyAgent

// Add request interceptor to handle NO_PROXY for axios
axios.interceptors.request.use(
(requestConfig) => {
const url = requestConfig.url
if (!url) {
return requestConfig
}

// Check if this URL should bypass the proxy
if (shouldBypassProxy(url, config.noProxy)) {
// Remove proxy agents for this request
requestConfig.httpAgent = undefined
requestConfig.httpsAgent = undefined
requestConfig.proxy = false
}

return requestConfig
},
(error) => {
return Promise.reject(error)
},
)

// Configure undici for fetch requests
configureUndiciProxy(config).catch((error) => {
logs.debug("Failed to configure undici proxy", "Proxy", { error })
})

logs.info("Proxy configuration complete (axios, fetch, undici)", "Proxy")
}

/**
* Sanitize proxy URL for logging (remove credentials)
*/
function sanitizeProxyUrl(proxyUrl: string): string {
try {
const url = new URL(proxyUrl)
if (url.username || url.password) {
return `${url.protocol}//*****:*****@${url.host}`
}
return proxyUrl
} catch {
return proxyUrl
}
}

/**
* Configure undici proxy (async to handle dynamic import)
*/
async function configureUndiciProxy(config: ProxyConfig): Promise<void> {
try {
// Configure undici (used by some providers like fetchWithTimeout)
const undici = await import("undici")
if (undici && undici.setGlobalDispatcher) {
const { ProxyAgent } = undici

// Use HTTPS proxy for all requests if configured, fallback to HTTP proxy
const proxyUri = config.httpsProxy || config.httpProxy
if (proxyUri) {
const proxyAgent = new ProxyAgent({
uri: proxyUri,
requestTls: {
rejectUnauthorized: config.rejectUnauthorized,
},
})
undici.setGlobalDispatcher(proxyAgent)
logs.debug("Undici proxy agent configured", "Proxy")
}
}
} catch (error) {
logs.debug("Undici not available or failed to configure", "Proxy", { error })
}
}

/**
* Check if proxy is configured
*/
export function isProxyConfigured(): boolean {
const config = getProxyConfig()
return !!(config.httpProxy || config.httpsProxy)
}
Loading