diff --git a/openspec/changes/headless-panel/.openspec.yaml b/openspec/changes/headless-panel/.openspec.yaml new file mode 100644 index 0000000..5c881ba --- /dev/null +++ b/openspec/changes/headless-panel/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-18 diff --git a/openspec/changes/headless-panel/design.md b/openspec/changes/headless-panel/design.md new file mode 100644 index 0000000..e1d8a27 --- /dev/null +++ b/openspec/changes/headless-panel/design.md @@ -0,0 +1,141 @@ +# Design: панель без iTerm2 + фон на клиенте + +Issue: #4. Ветка: `feat/4-headless-panel`. + +## Намерение + +Пользователь смотрит панель с iPad: Termius держит SSH local forward `8766 → 127.0.0.1:8766` на Mac, +страница открыта в Safari рядом с Termius (Split View). Все сессии Claude Code — в обычном tmux, +который работает сам по себе. Сейчас страница пропадает, если на Mac не запущен iTerm2, потому что +сервер поднимает скрипт AutoLaunch iTerm2. Переключение сессий с iPad уже работает — через tmux. +Нужно: страница доступна всегда, пока Mac включён и пользователь вошёл; фон меняется прямо на +странице, потому что тема по умолчанию в Safari раздражает. + +Успех: iTerm2 закрыт → в Safari на iPad видно дерево сессий, лимиты и баланс; клики переключают +tmux; фон выставлен под тему Termius и держится между перезагрузками. + +## Цели +- Фоновый сервер панели, независимый от iTerm2, автозапуск при входе, перезапуск при падении. +- Один владелец порта 8766; iTerm2-скрипт только регистрирует web view в Toolbelt. +- Шестерёнка ⚙ внизу панели (вариант A из мокапов): образцы фона, «свой цвет», «сброс»; + выбор хранится на устройстве; текст подстраивается под яркость фона. +- `install.sh`/`uninstall.sh` управляют службой; обновление старой установки без ручных шагов. + +## Не цели +- Досыл названий вкладок iTerm2 и цветов профиля в фоновый сервер (решено пользователем: + в tmux имена и так из tmux; цвет — шестерёнкой, в Toolbelt тоже). +- Доступ к странице из сети напрямую (только через туннель). +- Мод Claude Code / TUI-версия панели (обсуждались, отложены). +- Запуск службы без входа пользователя в систему на самом Mac (см. «Открытые вопросы»). + +## Анти-цели (провал, даже если «работает») +- Два сервера одновременно или удвоенный опрос лимитов (эндпоинт отвечает 429). +- Порт открыт наружу: в панели видны тексты сообщений сессий. +- Мелькание темы по умолчанию при каждой автоперезагрузке страницы (раз в минуту) после выбора фона. +- Установщик ломает чужие хуки/настройки или требует ручных шагов при обновлении. + +## Ограничения +- Python 3.8+, только стандартная библиотека; модуль `iterm2` — только в режиме регистратора. +- macOS, `launchctl`; LaunchAgent в сессии пользователя (Aqua). +- В LaunchAgent минимальный `PATH`: путь к `python3` фиксируется установщиком абсолютным; поиск + `tmux`/`ps` уже идёт через `EXTRA_PATH` в коде. +- Токен лимитов — из Keychain через `security`; служба должна работать в GUI-сессии пользователя, + где Keychain разблокирован. +- Сервер слушает только `127.0.0.1`. + +## Решения, принятые с пользователем +- Вариант 0 (SSH-туннель + Safari) — основной сценарий, мод/TUI не делаем. +- Фоновая служба через LaunchAgent; один сервер на порту; iTerm2-скрипт — регистратор. +- Цвет профиля iTerm2 не досылать и не читать (вариант 3): в Toolbelt фон тоже ставится шестерёнкой. +- Место настройки фона — шестерёнка в нижней строке (вариант A). +- Перезагрузка Mac без GUI-входа — не решаем сейчас, запоминаем на будущее. +- `install.sh` / `uninstall.sh` запускаются только за самим Mac (не по SSH). При запуске из SSH-сессии + (`SSH_CONNECTION`/`SSH_TTY` заданы) установщик предупреждает и завершается, ничего не меняя; + в README — строка об этом. + +## Как устроено (то, что решено) + +### Режимы `claude_agents.py` +Один исходный файл, два режима: +- **Фоновый сервер** (`--headless`, запускает launchd системным `python3`): `asyncio.start_server` на + `127.0.0.1:PORT`, `refresher(None)`, `usage_poller`, `balance_poller`; без `iterm2`, без + `theme_poller`, без регистрации web view. `live=None` → `build_tree` уже судит о сессиях по PID и + tmux. Блок `__main__` запускает этот режим при флаге независимо от наличия `iterm2`. +- **Регистратор** (запуск iTerm2 из AutoLaunch, без флага): проверяет, отвечает ли порт; если нет — + `launchctl kickstart gui//