Complete rewrite from tmux + JSONL parsing to a clean PTY-based approach. The proxy spawns Claude CLI in a pseudo-terminal via node-pty and relays terminal I/O as binary WebSocket frames to the simple-chat backend, which forwards them to the browser where xterm.js renders a full terminal. - node-pty PTY manager with 50KB replay buffer per session - Binary frame protocol with chatId multiplexing - WebSocket client with auto-reconnection and heartbeat - Directory listing and session listing for the web UI - README with setup instructions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
17 KiB
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 оркестрирует запуск:
- Загрузка конфигурации из
.env - Установка/обновление хука SessionStart для Claude CLI
- Подключение к бэкенду через WebSocket
- Инициализация tmux-сессии
- Запуск мониторинга активных окон
2. WebSocket-клиент (connection/ws-client.ts)
- Подключается к
SERVER_URLсPROXY_TOKENдля аутентификации - Обрабатывает входящие команды от бэкенда
- Отправляет события (сообщения, обновления статуса, скриншоты) на бэкенд
- Переподключается с экспоненциальной задержкой при разрыве соединения
3. Протокол (connection/protocol.ts)
Общие определения типов сообщений для WebSocket-протокола.
Сообщения от бэкенда к прокси
| Сообщение | Описание |
|---|---|
sync_state |
Отправить текущее состояние всех окон при подключении |
create_window |
Создать новое tmux-окно, запустить claude или claude --resume <id> |
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 <sessionId> - Удаление окна:
tmux kill-window -t <windowId> - Отправка клавиш:
tmux send-keys -t <windowId>для пользовательского ввода - Захват панели:
tmux capture-pane -t <windowId> -pдля скриншотов и строки состояния
Каждый чат в simple-chat соответствует одному tmux-окну. Идентификатор окна хранится в поле agentWindowId чата.
5. Монитор сессий (monitor/session-monitor.ts)
Оркестратор, запускающий два цикла опроса для каждого активного окна:
Опрос JSONL (интервал 2 секунды)
- Определение пути к JSONL-файлу по sessionId:
~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl - Чтение файла с последнего известного смещения в байтах (хранится в
monitor-state.ts) - Парсинг новых JSONL-записей с помощью
jsonl-parser.ts - Фильтрация системных/внутренних сообщений
- Отправка релевантных сообщений на бэкенд через WebSocket
Опрос терминала (интервал 1 секунда)
- Захват последних 3 строк tmux-панели (область строки состояния)
- Парсинг с помощью
terminal-parser.tsдля извлечения:- Строка 1: Название проекта, использование сессии %, использование за неделю %
- Строка 2: Название модели, использование контекста %
- Строка 3: Индикатор режима разрешений
- Обнаружение интерактивных состояний UI (запросы разрешений, запросы ввода)
- Отправка событий
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. Он:
- Получает sessionId от Claude CLI
- Определяет текущий идентификатор tmux-окна
- Записывает маппинг
{windowId: sessionId}в~/.claude-proxy/session_map.json
Этот маппинг необходим, потому что путь к JSONL-файлу зависит от sessionId, который становится известен только после запуска Claude CLI.
10. Карта сессий
Хранится в ~/.claude-proxy/session_map.json.
Сопоставляет идентификаторы tmux-окон с идентификаторами сессий Claude CLI:
{
"3": "abc123-def456",
"5": "789ghi-012jkl"
}
Используется монитором для нахождения правильного JSONL-файла транскрипта для каждого окна.
11. Загрузчик файлов (files/downloader.ts)
Скачивает файлы по URL из MinIO, на которые есть ссылки в сообщениях. Используется, когда вывод Claude CLI содержит ссылки на файлы, которые должны быть доступны через веб-интерфейс.
Потоки данных
Поток создания нового чата
- Пользователь создаёт чат в веб-интерфейсе simple-chat
- Бэкенд отправляет
create_windowпрокси через WebSocket - Прокси создаёт tmux-окно, запускает
claude(илиclaude --resume <id>) - Claude CLI запускается, срабатывает хук SessionStart
- Хук записывает маппинг sessionId -> windowId в session_map.json
- Прокси читает session_map, начинает мониторинг JSONL-файла и терминала
- Прокси отправляет
window_readyна бэкенд с windowId
Поток сообщений
- Пользователь вводит сообщение в simple-chat
- Бэкенд отправляет
send_messageпрокси - Прокси отправляет нажатия клавиш в tmux-окно через
tmux send-keys - Claude CLI обрабатывает сообщение, записывает в JSONL-транскрипт
- Опросчик JSONL обнаруживает новые байты, разбирает сообщения
- Прокси отправляет события
messageна бэкенд - Бэкенд передаёт их на фронтенд через SSE
Поток строки состояния
- Опросчик терминала захватывает последние 3 строки tmux-панели каждую секунду
- Парсер извлекает модель, контекст %, статистику использования
- Прокси отправляет
status_lineна бэкенд - Фронтенд отображает строку состояния в реальном времени в 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-клиент реализует экспоненциальную задержку:
- При разрыве соединения ожидание
baseDelay(начиная с ~1 секунды) - Каждая неудачная попытка переподключения удваивает задержку
- Ограничение максимальной задержкой
- При успешном переподключении отправляется
sync_stateс текущим состоянием окон - Сброс таймера задержки при успешном подключении
Интеграция с 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