<# build.ps1 - inline lib+gui into one script and compile to dist\ClaudeProxy.exe via ps2exe. Usage: powershell -ExecutionPolicy Bypass -File build.ps1 [-Smoke] -Smoke : after inlining, run the combined script with a 0.7s auto-close to catch runtime errors. #> [CmdletBinding()] param([switch]$Smoke) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $MyInvocation.MyCommand.Path $dist = Join-Path $root 'dist' New-Item -ItemType Directory -Force -Path $dist | Out-Null $rd = New-Object System.Text.UTF8Encoding($false) $enc = New-Object System.Text.UTF8Encoding($true) $lib = [System.IO.File]::ReadAllText((Join-Path $root 'claude-proxy.lib.ps1'), $rd) $guiLines = [System.IO.File]::ReadAllLines((Join-Path $root 'claude-proxy-gui.ps1'), $rd) # Drop the dot-source line and the $Root assignment: in a single-file exe there is no # external lib to source, and $MyInvocation.MyCommand.Path is null under ps2exe. $guiFiltered = $guiLines | Where-Object { ($_ -notmatch 'claude-proxy\.lib\.ps1') -and ($_ -notmatch '^\s*\$Root\s*=') } # embed the Xray binary (base64) so the exe is self-contained (no download needed) $embed = '' $embedFile = Join-Path $root 'xray-embed.b64' if (Test-Path $embedFile) { $b64 = ([System.IO.File]::ReadAllText($embedFile)).Trim() $embed = "`r`n`$script:XrayEmbedB64 = @'`r`n$b64`r`n'@`r`n" Write-Host ("Embedding Xray: {0} KB base64" -f [math]::Round($b64.Length / 1KB)) } else { Write-Host "xray-embed.b64 not found - exe will download Xray on first use." -ForegroundColor Yellow } $combined = $lib + $embed + "`r`n# ===== inlined GUI =====`r`n" + ($guiFiltered -join "`r`n") $combinedPath = Join-Path $dist 'claude-proxy.combined.ps1' [System.IO.File]::WriteAllText($combinedPath, $combined, $enc) Write-Host "Combined script -> $combinedPath" # parse-check $errs = $null; $tok = $null [System.Management.Automation.Language.Parser]::ParseFile($combinedPath, [ref]$tok, [ref]$errs) | Out-Null if ($errs) { Write-Host "PARSE ERRORS in combined script:" -ForegroundColor Red $errs | ForEach-Object { ' {0}:{1} {2}' -f $_.Extent.StartLineNumber, $_.Extent.StartColumnNumber, $_.Message } throw "Aborting build." } Write-Host "Parse: OK" -ForegroundColor Green if ($Smoke) { Write-Host "Smoke test (window will flash briefly)..." -ForegroundColor Cyan $env:CLAUDEPROXY_SMOKE = '1' & powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -File $combinedPath Remove-Item Env:\CLAUDEPROXY_SMOKE -ErrorAction SilentlyContinue Write-Host "Smoke exit: $LASTEXITCODE" } # regenerate the icon if the generator changed / icon missing $icon = Join-Path $root 'icon.ico' if (-not (Test-Path $icon)) { Write-Host "Generating icon..." -ForegroundColor Cyan & powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $root 'make-icon.ps1') | Out-Null } Import-Module ps2exe -ErrorAction Stop $exe = Join-Path $dist 'ClaudeProxy.exe' Write-Host "Compiling -> $exe" -ForegroundColor Cyan # NOTE: asInvoker (NOT requireAdmin). The GUI runs de-elevated so the Claude it launches is the # user's normal instance (matches the taskbar/tray pin). Firewall changes elevate on demand. $p2e = @{ inputFile = $combinedPath; outputFile = $exe noConsole = $true; STA = $true title = 'Claude Proxy'; product = 'Claude Proxy'; company = 'gusik' version = '1.0.0.0'; description = 'Force Claude desktop traffic through a proxy' } if (Test-Path $icon) { $p2e.iconFile = $icon } Invoke-ps2exe @p2e | Out-Null if (Test-Path $exe) { $sz = [math]::Round((Get-Item $exe).Length / 1KB) Write-Host "OK: $exe ($sz KB)" -ForegroundColor Green } else { throw "ps2exe did not produce the exe." }