Skip to content
Merged
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 .claude/hooks/install-dotnet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/bin/bash
# Install .NET 10 SDK and essential build tools for development
# This script is intended to be run as a Claude Code Web startup hook
set -euo pipefail

DOTNET_VERSION="10.0"
INSTALL_DIR="$HOME/.dotnet"

# Only run in remote Claude Code environments
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

# Install essential build tools if missing
if ! command -v tail &> /dev/null || ! command -v head &> /dev/null; then
echo "Installing essential build tools (coreutils)..."
apt-get update -qq && apt-get install -y -qq coreutils > /dev/null 2>&1 || true
fi

# Download and run the official Microsoft install script
curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
chmod +x /tmp/dotnet-install.sh

/tmp/dotnet-install.sh --channel $DOTNET_VERSION --install-dir "$INSTALL_DIR"

echo "Installing additional .NET runtime frameworks..."
/tmp/dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir "$INSTALL_DIR"
/tmp/dotnet-install.sh --channel 9.0 --runtime dotnet --install-dir "$INSTALL_DIR"

# Clean up
rm -f /tmp/dotnet-install.sh

# Add to PATH for current session
export DOTNET_ROOT="$INSTALL_DIR"
export PATH="$PATH:$INSTALL_DIR:$INSTALL_DIR/tools"

# Add to PATH in the CLAUDE_ENV_FILE
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo "export DOTNET_ROOT=\"$INSTALL_DIR\"" >> "$CLAUDE_ENV_FILE"
echo "export PATH=\"\$PATH:$INSTALL_DIR:$INSTALL_DIR/tools\"" >> "$CLAUDE_ENV_FILE"
fi

# Verify installation
echo ".NET SDK version $(dotnet --version) installed successfully at $INSTALL_DIR/dotnet"

# Configure NuGet to work with Claude Code proxy
# .NET's HttpClient doesn't properly handle proxy credentials from environment variables,
# so we configure NuGet to use localhost:8888 which will be handled by a proxy forwarder
echo "Configuring NuGet proxy settings..."
NUGET_CONFIG_DIR="$HOME/.nuget/NuGet"
NUGET_CONFIG_FILE="$NUGET_CONFIG_DIR/NuGet.Config"

mkdir -p "$NUGET_CONFIG_DIR"

cat > "$NUGET_CONFIG_FILE" << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<config>
<add key="http_proxy" value="http://127.0.0.1:8888" />
<add key="https_proxy" value="http://127.0.0.1:8888" />
</config>
</configuration>
EOF

echo "NuGet configuration updated at $NUGET_CONFIG_FILE"

# Start NuGet proxy forwarder in background
# This handles the proxy authentication that .NET's HttpClient doesn't support
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROXY_FORWARDER="$SCRIPT_DIR/nuget-proxy-forwarder.py"

if [ -f "$PROXY_FORWARDER" ]; then
# Check if forwarder is already running
if ! pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then
echo "Starting NuGet proxy forwarder on localhost:8888..."
nohup python3 "$PROXY_FORWARDER" > /dev/null 2>&1 &
sleep 1
if pgrep -f "nuget-proxy-forwarder.py" > /dev/null; then
echo "Proxy forwarder started successfully"
else
echo "Warning: Proxy forwarder may not have started correctly"
fi
else
echo "NuGet proxy forwarder is already running"
fi
else
echo "Warning: Proxy forwarder script not found at $PROXY_FORWARDER"
fi

exit 0
153 changes: 153 additions & 0 deletions .claude/hooks/nuget-proxy-forwarder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
NuGet Proxy Forwarder for Claude Code

.NET's HttpClient doesn't properly handle proxy credentials from environment variables.
This forwarder accepts unauthenticated connections on localhost:8888 and forwards them
to the authenticated Claude Code proxy with proper authentication headers.

Started automatically by install-dotnet.sh as a background daemon.
"""
import socket
import threading
import os
import base64
import sys
from urllib.parse import urlparse

def forward_data(source, destination):
"""Forward data from source socket to destination socket"""
try:
while True:
data = source.recv(4096)
if not data:
break
destination.sendall(data)
except:
pass

def handle_client(client_socket, client_addr, proxy_host, proxy_port, proxy_auth_header):
"""Handle a client connection"""
try:
# Read the CONNECT request
request = b""
while b"\r\n\r\n" not in request:
chunk = client_socket.recv(1)
if not chunk:
return
request += chunk

request_str = request.decode('utf-8', errors='ignore')
lines = request_str.split('\r\n')

if not lines[0].startswith('CONNECT'):
client_socket.close()
return

# Parse CONNECT target
target = lines[0].split(' ')[1]

# Connect to real proxy
proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxy_socket.connect((proxy_host, proxy_port))

# Forward CONNECT request with authentication
connect_request = f"CONNECT {target} HTTP/1.1\r\n"
connect_request += f"Host: {target}\r\n"
if proxy_auth_header:
connect_request += proxy_auth_header
connect_request += "\r\n"

proxy_socket.sendall(connect_request.encode())

# Read proxy response
response = b""
while b"\r\n\r\n" not in response:
chunk = proxy_socket.recv(1)
if not chunk:
client_socket.close()
proxy_socket.close()
return
response += chunk

# Forward response to client
client_socket.sendall(response)

response_str = response.decode('utf-8', errors='ignore')
status_line = response_str.split('\r\n')[0]

# If successful, start bidirectional forwarding
if "200" in status_line:
# Start forwarding threads
client_to_proxy = threading.Thread(target=forward_data, args=(client_socket, proxy_socket))
proxy_to_client = threading.Thread(target=forward_data, args=(proxy_socket, client_socket))

client_to_proxy.daemon = True
proxy_to_client.daemon = True

client_to_proxy.start()
proxy_to_client.start()

# Wait for either thread to finish
client_to_proxy.join()
proxy_to_client.join()

except Exception as e:
pass # Silently handle errors to avoid log spam
finally:
try:
client_socket.close()
except:
pass
try:
proxy_socket.close()
except:
pass

def main():
# Parse the real proxy from environment
proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('HTTP_PROXY')
if not proxy_url:
print("Error: No HTTPS_PROXY or HTTP_PROXY environment variable set", file=sys.stderr)
sys.exit(1)

proxy_uri = urlparse(proxy_url)
proxy_host = proxy_uri.hostname
proxy_port = proxy_uri.port or 80
proxy_auth = proxy_uri.username and proxy_uri.password

if proxy_auth:
auth_string = f"{proxy_uri.username}:{proxy_uri.password}"
proxy_auth_header = "Proxy-Authorization: Basic " + base64.b64encode(auth_string.encode()).decode() + "\r\n"
else:
proxy_auth_header = ""

# Create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

try:
server.bind(('127.0.0.1', 8888))
server.listen(5)
except OSError as e:
# Port already in use - another forwarder is running
sys.exit(0)

# Daemonize - close stdout/stderr to run silently in background
sys.stdout.close()
sys.stderr.close()

try:
while True:
client_sock, client_addr = server.accept()
client_thread = threading.Thread(
target=handle_client,
args=(client_sock, client_addr, proxy_host, proxy_port, proxy_auth_header)
)
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
server.close()

if __name__ == '__main__':
main()
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/install-dotnet.sh"
}
]
}
]
}
}
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sh eol=lf

###############################################################################
# Set default behavior for command prompt diff.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*.userosscache
*.sln.docstates

.claude/*.local.json

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

Expand Down
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="System.Data.SqlClient" Version="4.8.5" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ public DataStructure(

}

[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net60)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net10_0)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class EnumerableDataReaderBenchmark
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down