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

- Зачем:
  - 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.
This commit is contained in:
Dmitry Dementiev committed 2026-05-19 14:23:04 +03:00
1 parent dbc81c9c8f
commit 84e577bc97
1 file changed
+171
@@ -0,0 +1,171 @@
# Дизайн: `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` после
рефактора ведут себя как прежде.