feat(proxy): добавлены pclaude/pcodex для явного запуска через прокси

- Зачем:
  - claude/codex должны ходить через hproxy.local, а ручной proxy_on легко забыть; нужны явные команды, которые не стартуют молча при недоступном прокси.
- Что:
  - bash: добавлены _proxy_export/_proxy_reachable/_proxy_run и pclaude/pcodex; proxy_on рефакторен на единый источник правды без смены поведения.
  - powershell: добавлены Get-ProxyEndpoint/Set-ProxyEnv/Test-ProxyReachable/Invoke-WithProxy и pclaude/pcodex; proxy_on рефакторен аналогично.
  - таймаут pre-flight вынесен в PROXY_PREFLIGHT_TIMEOUT (дефолт 2 c).
  - добавлен tests/test_proxy_launchers.sh (fail-fast, изоляция env, проброс кода, аргументы, регресс proxy_on).
- Проверка:
  - bash tests/test_proxy_launchers.sh.
This commit is contained in:
Dmitry Dementiev committed 2026-05-19 14:28:41 +03:00
1 parent 84e577bc97
commit d5559867bd
3 files changed
+249 -18

No files matched your search

+80 -7
View File
@@ -75,15 +75,46 @@ function Get-PublicIp {
catch { return $null }
}
# Единый источник правды для прокси-эндпоинта.
function Get-ProxyEndpoint {
@{
ProxyHost = 'hproxy.local'
Port = 12334
NoProxy = 'hproxy.local,localhost,127.0.0.1,.concept-software.ru'
}
}
# Внутренний: выставить proxy-env в текущей области. Единственное место,
# где формируются значения переменных.
function Set-ProxyEnv {
$e = Get-ProxyEndpoint
$env:HTTP_PROXY = "http://$($e.ProxyHost):$($e.Port)"
$env:HTTPS_PROXY = "http://$($e.ProxyHost):$($e.Port)"
$env:ALL_PROXY = "socks5://$($e.ProxyHost):$($e.Port)"
$env:NO_PROXY = $e.NoProxy
}
# Внутренний: fail-fast проверка доступности прокси (только reachability).
# Таймаут — $env:PROXY_PREFLIGHT_TIMEOUT (дефолт 2 c), покрывает и медленный DNS.
function Test-ProxyReachable {
$e = Get-ProxyEndpoint
$timeout = if ($env:PROXY_PREFLIGHT_TIMEOUT) { [int]$env:PROXY_PREFLIGHT_TIMEOUT } else { 2 }
$client = [System.Net.Sockets.TcpClient]::new()
try {
$iar = $client.BeginConnect($e.ProxyHost, [int]$e.Port, $null, $null)
if ($iar.AsyncWaitHandle.WaitOne([TimeSpan]::FromSeconds($timeout))) {
$client.EndConnect($iar)
return $true
}
return $false
}
catch { return $false }
finally { $client.Dispose() }
}
# Включить прокси для текущей сессии (hproxy.local).
function proxy_on {
$Host_ = 'hproxy.local'; $Port = 12334
$env:HTTP_PROXY = "http://${Host_}:${Port}"
$env:HTTPS_PROXY = "http://${Host_}:${Port}"
$env:ALL_PROXY = "socks5://${Host_}:${Port}"
$env:NO_PROXY = "${Host_},localhost,127.0.0.1,.concept-software.ru"
Set-ProxyEnv
$Ip = Get-PublicIp
"Proxy ON. IP: $(if ($Ip) { $Ip } else { 'unavailable' })"
}
@@ -103,6 +134,48 @@ function myip {
"IP: $(if ($Ip) { $Ip } else { 'unavailable' })"
}
# Внутренний: запуск команды через прокси. Pre-flight, затем env только на
# время дочернего процесса (в finally восстанавливаем прежние значения —
# сессия не остаётся с включённым прокси).
function Invoke-WithProxy {
param(
[Parameter(Mandatory)][string]$Exe,
[Parameter(ValueFromRemainingArguments)][string[]]$CmdArgs
)
if (-not (Get-Command $Exe -ErrorAction SilentlyContinue)) {
Write-Error "Команда не найдена в PATH: $Exe"
return
}
if (-not (Test-ProxyReachable)) {
$e = Get-ProxyEndpoint
Write-Error "Прокси $($e.ProxyHost):$($e.Port) недоступен — запуск '$Exe' отменён."
return
}
$names = 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY'
$saved = @{}
foreach ($n in $names) { $saved[$n] = [Environment]::GetEnvironmentVariable($n) }
try {
Set-ProxyEnv
& $Exe @CmdArgs
$global:LASTEXITCODE = $LASTEXITCODE
}
finally {
foreach ($n in $names) {
if ($null -eq $saved[$n]) {
Remove-Item -Path "Env:$n" -ErrorAction SilentlyContinue
} else {
Set-Item -Path "Env:$n" -Value $saved[$n]
}
}
}
}
# Явный запуск claude/codex гарантированно через прокси.
function pclaude { Invoke-WithProxy claude @args }
function pcodex { Invoke-WithProxy codex @args }
# ── Шорткаты Windows Terminal ────────────────────────────────────────
if (Get-Command wt -ErrorAction SilentlyContinue) {