diff --git a/.claude/hooks/install-dotnet.sh b/.claude/hooks/install-dotnet.sh
new file mode 100755
index 0000000..ceeaafd
--- /dev/null
+++ b/.claude/hooks/install-dotnet.sh
@@ -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'
+
+
+
+
+
+
+
+
+
+
+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
diff --git a/.claude/hooks/nuget-proxy-forwarder.py b/.claude/hooks/nuget-proxy-forwarder.py
new file mode 100755
index 0000000..d44a7e1
--- /dev/null
+++ b/.claude/hooks/nuget-proxy-forwarder.py
@@ -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()
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..8a552bb
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,15 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "matcher": "startup",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/install-dotnet.sh"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.gitattributes b/.gitattributes
index 1ff0c42..0d77001 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
+*.sh eol=lf
###############################################################################
# Set default behavior for command prompt diff.
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index e0c0049..9536938 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -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
diff --git a/.gitignore b/.gitignore
index 3c4efe2..5ef2421 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@
*.userosscache
*.sln.docstates
+.claude/*.local.json
+
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 01f75c3..c8ec184 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,11 +3,11 @@
true
-
-
-
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj
index 792a017..3150f11 100644
--- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj
+++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs
index 5bd483e..67f3b81 100644
--- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs
+++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs
@@ -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
diff --git a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj
index 39b1548..d2aec16 100644
--- a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj
+++ b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj
@@ -1,7 +1,7 @@
- net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
diff --git a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj
index c708326..ac0cff8 100644
--- a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj
+++ b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj
@@ -1,7 +1,7 @@
- net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
false