Files
claude-cli-proxy/ARCHITECTURE.md
vigdorov 4a91896732 feat: xterm.js + node-pty architecture for Claude CLI web terminal
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>
2026-03-22 00:24:29 +03:00

17 KiB
Raw Blame History

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 <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 секунды)

  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:

{
  "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 <id>)
  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