# Claude CLI Proxy — Архитектура ## Обзор claude-cli-proxy — это локальное приложение на Node.js, которое соединяет Claude Code CLI с веб-интерфейсом simple-chat. Оно подключается к бэкенду simple-chat через исходящее WebSocket-соединение, управляет tmux-сессиями для запуска экземпляров Claude CLI и отслеживает их вывод через JSONL-файлы транскриптов и захват терминала. ## Диаграмма компонентов ``` ┌─────────────────────────────────────────────────────────────────┐ │ Mac разработчика │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ claude-cli-proxy │ │ │ │ │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ WsClient │ │ TmuxManager │ │SessionMonitor│ │ │ │ │ │(connection/)│ │ (tmux/) │ │ (monitor/) │ │ │ │ │ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴───────┐ ┌──────┴───────┐ │ │ │ │ │ │ tmux-сессия │ │ JSONL-файлы │ │ │ │ │ │ │"claude-proxy"│ │ ~/.claude/ │ │ │ │ │ │ │ ├─ окно 1 │ │ projects/... │ │ │ │ │ │ │ ├─ окно 2 │ └──────────────┘ │ │ │ │ │ │ └─ ... │ │ │ │ │ │ └──────────────┘ │ │ │ └─────────┼────────────────────────────────────────────────┘ │ │ │ WebSocket │ └────────────┼────────────────────────────────────────────────────┘ │ ┌────────┴────────┐ │ simple-chat │ │ бэкенд │ │ (модуль │ │ agent-proxy) │ └─────────────────┘ ``` ## Компоненты ### 1. Точка входа (`main.ts`) Класс `ClaudeCliProxy` оркестрирует запуск: 1. Загрузка конфигурации из `.env` 2. Установка/обновление хука SessionStart для Claude CLI 3. Подключение к бэкенду через WebSocket 4. Инициализация tmux-сессии 5. Запуск мониторинга активных окон ### 2. WebSocket-клиент (`connection/ws-client.ts`) - Подключается к `SERVER_URL` с `PROXY_TOKEN` для аутентификации - Обрабатывает входящие команды от бэкенда - Отправляет события (сообщения, обновления статуса, скриншоты) на бэкенд - Переподключается с экспоненциальной задержкой при разрыве соединения ### 3. Протокол (`connection/protocol.ts`) Общие определения типов сообщений для WebSocket-протокола. #### Сообщения от бэкенда к прокси | Сообщение | Описание | |-----------|----------| | `sync_state` | Отправить текущее состояние всех окон при подключении | | `create_window` | Создать новое tmux-окно, запустить `claude` или `claude --resume ` | | `send_message` | Отправить текстовый ввод в Claude CLI в указанном окне | | `send_command` | Отправить произвольную команду в tmux-панель | | `send_command_capture` | Отправить команду и захватить вывод | | `send_key` | Отправить нажатие клавиши (например, Enter, Escape, Tab) | | `kill_window` | Закрыть tmux-окно | | `request_screenshot` | Захватить текущее содержимое терминала | | `list_directories` | Получить список доступных рабочих директорий | | `list_sessions` | Получить список возобновляемых сессий Claude | #### Сообщения от прокси к бэкенду | Сообщение | Описание | |-----------|----------| | `window_ready` | Новое окно создано, сообщается windowId | | `message` | Новое сообщение ассистента/пользователя из JSONL-транскрипта | | `status` | Изменение статуса Claude CLI (размышление, использование инструмента, простой) | | `interactive` | Обнаружен интерактивный UI (запрос разрешения и т.д.) | | `status_line` | Разобранные данные строки состояния (модель, контекст %, сессия %) | | `screenshot` | Скриншот терминала (захваченное содержимое панели) | | `window_closed` | Окно было закрыто или процесс завершился | | `directories` | Ответ на list_directories | | `sessions_list` | Ответ на list_sessions | | `command_output` | Ответ на send_command_capture | | `error` | Отчёт об ошибке | ### 4. Менеджер Tmux (`tmux/manager.ts`) Управляет tmux-сессией `claude-proxy` (настраивается через `TMUX_SESSION`). Операции: - **Создание окна:** `tmux new-window` с командой `claude` или `claude --resume ` - **Удаление окна:** `tmux kill-window -t ` - **Отправка клавиш:** `tmux send-keys -t ` для пользовательского ввода - **Захват панели:** `tmux capture-pane -t -p` для скриншотов и строки состояния Каждый чат в simple-chat соответствует одному tmux-окну. Идентификатор окна хранится в поле `agentWindowId` чата. ### 5. Монитор сессий (`monitor/session-monitor.ts`) Оркестратор, запускающий два цикла опроса для каждого активного окна: #### Опрос JSONL (интервал 2 секунды) 1. Определение пути к JSONL-файлу по sessionId: `~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl` 2. Чтение файла с последнего известного смещения в байтах (хранится в `monitor-state.ts`) 3. Парсинг новых JSONL-записей с помощью `jsonl-parser.ts` 4. Фильтрация системных/внутренних сообщений 5. Отправка релевантных сообщений на бэкенд через WebSocket #### Опрос терминала (интервал 1 секунда) 1. Захват последних 3 строк tmux-панели (область строки состояния) 2. Парсинг с помощью `terminal-parser.ts` для извлечения: - **Строка 1:** Название проекта, использование сессии %, использование за неделю % - **Строка 2:** Название модели, использование контекста % - **Строка 3:** Индикатор режима разрешений 3. Обнаружение интерактивных состояний UI (запросы разрешений, запросы ввода) 4. Отправка событий `status_line`, `status` и `interactive` на бэкенд ### 6. JSONL-парсер (`monitor/jsonl-parser.ts`) Разбирает файлы транскриптов Claude CLI в формате JSONL. Каждая строка — это JSON-объект, представляющий событие разговора. Парсер: - Читает новые байты с последнего смещения - Разбирает каждую строку как JSON - Фильтрует системные сообщения (инициализация, конфигурация и т.д.) - Извлекает сообщения пользователя, сообщения ассистента, вызовы инструментов и результаты инструментов ### 7. Парсер терминала (`monitor/terminal-parser.ts`) Разбирает строку состояния Claude CLI, отображаемую в нижней части терминала: ``` Строка 1: agent_dev │ session: 24% │ week: 3% Строка 2: Opus 4.6 (1M context) │ Ctx: 2% Строка 3: ⏵⏵ bypass permissions on (shift+tab to cycle) ``` Извлекает структурированные данные: название проекта, модель, процент контекста, использование сессии/недели, режим разрешений. Также обнаруживает интерактивные состояния UI, когда Claude ожидает ввода пользователя (запросы разрешений, вопросы да/нет). ### 8. Состояние монитора (`monitor/monitor-state.ts`) Отслеживает состояние мониторинга для каждого окна: - **Смещение в байтах:** Последняя позиция чтения в JSONL-файле (для инкрементального чтения) - **Кеш mtime:** Время модификации файла (пропуск повторного чтения неизменённых файлов) ### 9. Система хуков (`hook/`) #### hook.ts Управляет хуком SessionStart для Claude CLI: - Устанавливает/обновляет запись хука в `~/.claude/settings.json` - Предоставляет CRUD-операции для `~/.claude-proxy/session_map.json` #### session-start-hook.ts Скрипт, который запускается при старте новой сессии Claude CLI. Он: 1. Получает sessionId от Claude CLI 2. Определяет текущий идентификатор tmux-окна 3. Записывает маппинг `{windowId: sessionId}` в `~/.claude-proxy/session_map.json` Этот маппинг необходим, потому что путь к JSONL-файлу зависит от sessionId, который становится известен только после запуска Claude CLI. ### 10. Карта сессий Хранится в `~/.claude-proxy/session_map.json`. Сопоставляет идентификаторы tmux-окон с идентификаторами сессий Claude CLI: ```json { "3": "abc123-def456", "5": "789ghi-012jkl" } ``` Используется монитором для нахождения правильного JSONL-файла транскрипта для каждого окна. ### 11. Загрузчик файлов (`files/downloader.ts`) Скачивает файлы по URL из MinIO, на которые есть ссылки в сообщениях. Используется, когда вывод Claude CLI содержит ссылки на файлы, которые должны быть доступны через веб-интерфейс. ## Потоки данных ### Поток создания нового чата 1. Пользователь создаёт чат в веб-интерфейсе simple-chat 2. Бэкенд отправляет `create_window` прокси через WebSocket 3. Прокси создаёт tmux-окно, запускает `claude` (или `claude --resume `) 4. Claude CLI запускается, срабатывает хук SessionStart 5. Хук записывает маппинг sessionId -> windowId в session_map.json 6. Прокси читает session_map, начинает мониторинг JSONL-файла и терминала 7. Прокси отправляет `window_ready` на бэкенд с windowId ### Поток сообщений 1. Пользователь вводит сообщение в simple-chat 2. Бэкенд отправляет `send_message` прокси 3. Прокси отправляет нажатия клавиш в tmux-окно через `tmux send-keys` 4. Claude CLI обрабатывает сообщение, записывает в JSONL-транскрипт 5. Опросчик JSONL обнаруживает новые байты, разбирает сообщения 6. Прокси отправляет события `message` на бэкенд 7. Бэкенд передаёт их на фронтенд через SSE ### Поток строки состояния 1. Опросчик терминала захватывает последние 3 строки tmux-панели каждую секунду 2. Парсер извлекает модель, контекст %, статистику использования 3. Прокси отправляет `status_line` на бэкенд 4. Фронтенд отображает строку состояния в реальном времени в UI чата ## Кодирование пути JSONL Claude CLI хранит транскрипты по пути: ``` ~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl ``` Кодирование пути: символы `/` и `_` в пути рабочей директории заменяются на `-`. Пример: - Рабочая директория: `/Users/vigdorov/agent_dev` - Закодированный путь: `-Users-vigdorov-agent-dev` - Полный путь: `~/.claude/projects/-Users-vigdorov-agent-dev/{sessionId}.jsonl` ## Логика переподключения WebSocket-клиент реализует экспоненциальную задержку: 1. При разрыве соединения ожидание `baseDelay` (начиная с ~1 секунды) 2. Каждая неудачная попытка переподключения удваивает задержку 3. Ограничение максимальной задержкой 4. При успешном переподключении отправляется `sync_state` с текущим состоянием окон 5. Сброс таймера задержки при успешном подключении ## Интеграция с simple-chat ### Бэкенд - Модуль: `agent-proxy` - WebSocket gateway обрабатывает подключения прокси - REST API для управления agent-устройствами - SSE relay передаёт обновления в реальном времени на фронтенд ### Фронтенд - Маршрут: `/agent` - `AgentPage` — список устройств/чатов - `AgentChatPage` — вид чата с сообщениями в реальном времени и строкой состояния - Slash-команды: `/model` (выбор модели), `/resume` (выбор сессии), `/clear`, `/compact`, `/plan` ### База данных - Таблица `agent_devices` — зарегистрированные прокси-устройства - Поля таблицы `chats`: `agentWindowId`, `agentWorkDir`, `agentSessionId`