- Зачем: - claude/codex CLI должны гарантированно ходить через hproxy.local, а ручной proxy_on легко забыть. - Что: - добавлен spec docs/superpowers/specs/2026-05-19-proxy-cli-launchers-design.md. - описаны команды pclaude/pcodex с fail-fast pre-flight для Linux (bash) и Windows (PowerShell). - честная формулировка цели (удобство + best-effort), зафиксировано проверенное допущение о proxy-env. - добавлены секция «Известные ограничения» (аддитивность, TOCTOU), PS-passthrough и регресс-тест proxy_on. - Проверка: - git show --stat HEAD.
172 lines
12 KiB
Markdown
172 lines
12 KiB
Markdown
# Дизайн: `pclaude` / `pcodex` — явный запуск claude/codex CLI через прокси
|
||
|
||
Дата: 2026-05-19
|
||
Статус: утверждён (дизайн), ожидает плана реализации
|
||
|
||
## Проблема
|
||
|
||
`claude` и `codex` CLI должны ходить в сеть через прокси
|
||
(`hproxy.local:12334`). Сейчас это обеспечивается ручным `proxy_on` в текущем
|
||
shell — легко забыть, и тогда трафик уходит мимо прокси.
|
||
|
||
## Цель и подход
|
||
|
||
Цель — **удобство и best-effort**, а не сетевая гарантия: дать одну явную
|
||
команду, после которой не нужно помнить про `proxy_on` и которая не стартует
|
||
молча, если прокси недоступен. Это **не** fail-closed изоляция — голые
|
||
`claude`/`codex` по-прежнему могут уйти мимо прокси (см. «Известные
|
||
ограничения»).
|
||
|
||
**Проверенное допущение:** `claude` и `codex` уважают
|
||
`HTTP(S)_PROXY`/`ALL_PROXY` — подтверждено эмпирически (ежедневная работа в
|
||
этом режиме). На этом стоит весь env-подход; spike не требуется.
|
||
|
||
Ввести **отдельные явные команды** `pclaude` и `pcodex`. Бинарники
|
||
`claude`/`codex` не переименовываются и не перекрываются — явное `p`-имя
|
||
выражает намерение и исключает риск «где-то нет алиаса → запуск мимо прокси».
|
||
|
||
Подход — **convenience-обёртка** (не сетевая изоляция):
|
||
|
||
1. выставить proxy-env только для дочернего процесса (не загрязняя
|
||
родительский shell/сессию);
|
||
2. **fail-fast pre-flight**: перед запуском быстро проверить, что
|
||
`hproxy.local:12334` доступен; если нет — **не запускать**, выдать
|
||
понятную ошибку и ненулевой код возврата. Семантика проверки —
|
||
*reachability* (порт прокси открыт), а не доказательство, что трафик
|
||
реально проксируется; последнее покрывается проверенным допущением выше.
|
||
Таймаут вынесен в переменную (`PROXY_PREFLIGHT_TIMEOUT`, дефолт 2 c).
|
||
|
||
Реализация на двух платформах:
|
||
|
||
- Linux/WSL bash;
|
||
- Windows native PowerShell.
|
||
|
||
Эндпоинт прокси один и тот же на обеих платформах (`hproxy.local:12334`) и
|
||
переиспользуется из уже существующих `proxy_on`.
|
||
|
||
### Не входит в объём (YAGNI)
|
||
|
||
- Сетевая изоляция / firewall / network namespace (fail-closed).
|
||
- Перекрытие/переименование оригинальных `claude`/`codex`.
|
||
- Поддержка не-PowerShell оболочек на Windows (cmd.exe и т.п.).
|
||
- Изменение существующего поведения `proxy_on`/`proxy_off`/`myip`.
|
||
|
||
## Ключевой принцип: единый источник правды
|
||
|
||
Эндпоинт прокси сейчас захардкожен в `proxy_on` дважды (bash и PS-профиль).
|
||
Чтобы `pclaude`/`pcodex` не размножили константы ещё в нескольких местах,
|
||
значения выносятся в небольшой внутренний хелпер в каждом файле, и
|
||
существующий `proxy_on` рефакторится на этот же хелпер. Это единственный
|
||
сопутствующий рефактор, ограниченный задачей.
|
||
|
||
## Компоненты — Linux
|
||
|
||
Файл: `bash/.bash_functions` (stow → `~/.bash_functions`, уже подключается
|
||
через `~/.bash_custom`). Новых установочных шагов нет.
|
||
|
||
1. `_proxy_export()` — внутренний. Экспортирует `HTTP_PROXY`, `HTTPS_PROXY`,
|
||
`ALL_PROXY`, `NO_PROXY` (+ lower-case дубли) из единого набора локальных
|
||
переменных. `proxy_on()` рефакторится так, чтобы вызывать `_proxy_export`;
|
||
его эхо и проверка внешнего IP остаются без изменений.
|
||
2. `_proxy_reachable()` — TCP-connect к `hproxy.local:12334` с таймаутом
|
||
`${PROXY_PREFLIGHT_TIMEOUT:-2}` c
|
||
(`timeout "$t" bash -c '... >/dev/tcp/hproxy.local/12334'`). Возвращает
|
||
0/1, ничего не печатает. Таймаут покрывает и медленный DNS-резолв
|
||
`hproxy.local` (при зависании резолва — false-negative и отказ запуска,
|
||
что является безопасным направлением отказа).
|
||
3. `_proxy_run <cmd> [args…]`:
|
||
- `_proxy_reachable` → при неудаче печатает понятное сообщение
|
||
(рус., стиль существующих `❌`/`✅`), `return 1`, **бинарник не
|
||
запускается**;
|
||
- проверка наличия `<cmd>` в PATH (`command -v`) → при отсутствии
|
||
отдельное понятное сообщение, ненулевой код;
|
||
- запуск в subshell: `( _proxy_export; exec command "<cmd>" "$@" )` —
|
||
proxy-env виден только дочернему процессу, не протекает в родительский
|
||
shell; код возврата пробрасывается.
|
||
4. `pclaude() { _proxy_run claude "$@"; }`
|
||
`pcodex() { _proxy_run codex "$@"; }`
|
||
|
||
## Компоненты — Windows
|
||
|
||
Файл: `chezmoi/dot_config/powershell/profile.ps1` (уже доставляется chezmoi и
|
||
подключается через `Microsoft.PowerShell_profile.ps1.tmpl`). Новых
|
||
установочных шагов нет.
|
||
|
||
1. `Set-ProxyEnv` — внутренний. Ставит `HTTP_PROXY`, `HTTPS_PROXY`,
|
||
`ALL_PROXY`, `NO_PROXY` из единого источника. `proxy_on` рефакторится на
|
||
него (его вывод/`Get-PublicIp` не меняются).
|
||
2. `Test-ProxyReachable` — `System.Net.Sockets.TcpClient` к
|
||
`hproxy.local:12334` с таймаутом ~2 c. Возвращает `$true`/`$false`.
|
||
3. `Invoke-WithProxy -Exe <name> -CmdArgs $args`:
|
||
- pre-flight `Test-ProxyReachable` → при неудаче `Write-Error` + return,
|
||
**не запускать**;
|
||
- проверка `Get-Command <name>` → при отсутствии понятная ошибка;
|
||
- сохранить текущие значения 4 env-переменных; `Set-ProxyEnv`;
|
||
`& $Exe @CmdArgs`; в `finally` восстановить прежние значения env
|
||
(сессия не остаётся с включённым прокси — поведение симметрично
|
||
subshell в bash); пробросить `$LASTEXITCODE`.
|
||
4. `function pclaude { Invoke-WithProxy claude @args }`
|
||
`function pcodex { Invoke-WithProxy codex @args }`
|
||
|
||
**Passthrough аргументов — known footgun PowerShell.** Флаги вида `-p`,
|
||
`--model` могут перехватываться парсером PS как параметры обёртки.
|
||
Требование: `Invoke-WithProxy` объявляет `[Parameter(ValueFromRemainingArguments)]`
|
||
на `$CmdArgs`, обёртки передают `@args` (splatting, не `$args`), внутри —
|
||
`& $Exe @CmdArgs`. Обязательные тест-кейсы при реализации:
|
||
`pclaude -p "..."`, `pclaude --model ...`, `pcodex --help`, аргумент с
|
||
пробелами и с `--`.
|
||
|
||
## Обработка ошибок
|
||
|
||
| Ситуация | Поведение |
|
||
|---|---|
|
||
| Прокси `hproxy.local:12334` недоступен | Понятное сообщение, ненулевой код, **запуск не происходит** |
|
||
| `claude`/`codex` нет в PATH | Отдельное понятное сообщение, ненулевой код |
|
||
| Аргументы с пробелами/спецсимволами | Сохраняются (`"$@"` / `@args`-splatting) |
|
||
| `NO_PROXY` | Берётся существующий список платформы как есть (bash: с `::1`/`[::1]`; PS: без) — существующее поведение не меняется |
|
||
|
||
## Известные ограничения
|
||
|
||
Принимаются осознанно (следствие выбранного convenience-подхода):
|
||
|
||
- **Аддитивность.** Голые `claude`/`codex` остаются доступны и идут мимо
|
||
прокси без предупреждения. Фича снижает вероятность ошибки, но не
|
||
устраняет её — это удобство, не гарантия.
|
||
- **TOCTOU.** Pre-flight проверяет доступность только в момент старта.
|
||
Для долгой интерактивной сессии `claude` прокси может упасть позже —
|
||
обёртка этого не отследит.
|
||
- **Pre-flight ≠ доказательство проксирования.** Проверяется открытость
|
||
порта, не факт, что инструмент использует прокси (закрыто проверенным
|
||
допущением о поведении claude/codex, не самим preflight).
|
||
|
||
## Деплой
|
||
|
||
Ноль новых установочных шагов. Оба целевых файла уже доставляются (stow /
|
||
chezmoi) и подключены в shell-профилях. `run_`-скрипты chezmoi не требуются.
|
||
|
||
## Тестирование
|
||
|
||
Зеркалит существующий стиль (`tests/test_install.sh` — простой bash +
|
||
assert-хелперы, без bats).
|
||
|
||
Новый `tests/test_proxy_launchers.sh`:
|
||
|
||
- `_proxy_run` при недоступном прокси не вызывает бинарник и возвращает
|
||
ненулевой код (мок проверки доступности + мок `command`/PATH);
|
||
- proxy-env реально выставлены в дочернем процессе и **не** протекают в
|
||
родительский shell;
|
||
- проброс кода возврата дочернего процесса;
|
||
- аргументы с пробелами и `--` доходят до бинарника без искажений;
|
||
- **регресс `proxy_on`**: после рефактора на общий хелпер `proxy_on`
|
||
экспортирует ровно тот же набор переменных (включая lower-case дубли и
|
||
тот же `NO_PROXY`), что и до рефактора — снимок до/после;
|
||
- `shellcheck` на `bash/.bash_functions`.
|
||
|
||
Windows-часть — без авто-тестов (нет CI под Windows). Ручной чеклист в
|
||
плане: `pclaude`/`pcodex` запускаются; при остановленном прокси fail-fast
|
||
срабатывает; env не остаётся в сессии после выхода (в т.ч. после Ctrl-C —
|
||
если `finally` не отработал, прокси остаётся включённым: безопасное
|
||
направление отказа); passthrough-кейсы `pclaude -p "..."`,
|
||
`pcodex --help`, аргумент с пробелами; `proxy_on`/`proxy_off` после
|
||
рефактора ведут себя как прежде.
|