Трекер-веб: фронт и общая логика

Дубль локального таск-трекера (FastAPI + SQLite) на PHP 8.0+ и MySQL для размещения в сети: веб-страница и API для человека и Claude, плюс Telegram-бот (уведомления, команды, кнопки подтверждения) и скрипт-диспетчер, который будит Claude Code при новых записях. Подробности по маршрутам — в API.md.

Содержание

- Режим «Чат»

  1. Telegram-бот
  2. Диспетчер для Claude
  3. Потоки данных
  4. Ключевые решения и почему
  5. Установка
  6. Тесты
  7. Что заливать на сервер

Зачем и как всё устроено

Идея: Claude Code живёт на отдельном сервере (VPS) и работает не в диалоге, а по очереди поручений. Человек пишет задачу или комментарий в трекере (в браузере или в Telegram), Claude подхватывает, работает, отвечает комментариями и вопросами в том же трекере, а в Telegram приходят уведомления.

 человек                          PHP-хостинг                         VPS (Claude Code)
┌────────┐   браузер    ┌──────────────────────────┐   HTTPS+ключ   ┌──────────────────┐
│ браузер├─────────────►│ index.php + api.php      │◄───────────────┤ dispatcher.py    │
│        │              │ MySQL (эпики, задачи…)   │                │  раз в минуту →  │
│Telegram├─────────────►│ bot/webhook.php          │                │  claude -p …     │
└───▲────┘   сообщения  │ bot/notify.php ──────────┼──► Telegram    └──────────────────┘
    └───────────────────┴──────────────────────────┘  уведомления

Трекер и бот стоят на обычном хостинге с HTTPS (всегда доступны, не зависят от VPS), Claude с диспетчером — на VPS. Связь только по API с ключом, поэтому позже всё можно перенести на один сервер, поменяв один адрес в конфиге диспетчера.

Структура файлов

index.php            страница (HTML-каркас; версия css/js по времени файла)
api.php              единая точка API: авторизация, транзакция, разбор маршрута
file.php             скачивание вложений (после проверки входа)
install.php          создание таблиц (php install.php или install.php?key=…)
router.php           только для локального встроенного сервера PHP
config.example.php   образец настроек; config.php — настоящий (в git не попадает)
.htaccess            api/… → api.php, закрыт config.php, заголовок Authorization
lib/
  db.php             PDO, помощники q/rows/one/val/insert/tx, время now()
  schema.php         схема таблиц и правила по умолчанию
  core.php           вся бизнес-логика (эпики, задачи, комментарии, поиск, файлы)
  chat.php           чат с Claude: чаты, сообщения, разрешения
  secrets.php        секреты: PIN, список, правка (только user)
  rules.php          правила переходов статусов
  dedupe.php         защита от повторных вопросов
  auth.php           ключи, сессия, CSRF, защита от перебора пароля
  routes.php         таблица маршрутов API
static/
  app.js, app.css    интерфейс (ванильный JS, без библиотек и CDN), режимы «Трекер/Чат»
  chat.js            режим «Чат»: список, переписка, стриминг, карточки разрешений
  secrets.js         режим «Секреты»: PIN, список по проектам, правка
  cookie-consent.js  плашка согласия на cookie
bot/
  Telegram.php       универсальный клиент Bot API (один файл, без зависимостей — копируется в другие проекты)
  notify.php         клиент из config, карточки, уведомления о событиях
  commands.php       команды и кнопки бота
  webhook.php        приёмник Telegram
  set_webhook.php    регистрация приёмника, меню команд, статус
  poll.php           приём опросом для локальной проверки
  tg_cli.php         консольная отправка: me / send / updates
dispatcher/          dispatcher.py (будит Claude по inbox), chat_bridge.py (мост чата), fake_claude.py, config.example.json, chat_bridge.config.example.json, тесты
tools/               make_secrets.php, export_sqlite.py + import.php, live_check.php, build_docs.py
tests/               run.php (автотесты), config.test.php
docs/                эта документация
files/               вложения (закрыта от прямого доступа)

Вся логика лежит в lib/core.php и не знает про HTTP: API (api.php) и бот (bot/commands.php) вызывают одни и те же функции. Поэтому правила статусов, дедупликация вопросов и запреты работают одинаково и в вебе, и в Telegram.

Веб-страница

Один экран с тремя зонами; приложение прячется за входом по паролю.

Telegram-бот

Бот отвечает только чатам из telegram.allowed_chat_ids (остальным — молчание) и дублирует базовые функции трекера.

Команды

КомандаЧто делает
/epicsоткрытые эпики (не done): статус, задач выполнено/всего, сколько в работе, баги, ❓ открытые вопросы; кнопки #N для открытия карточки
/epic 49карточка: статус, описание, задачи со статусами, открытые баги, вопросы; кнопки
/qвсе открытые вопросы — каждый отдельным сообщением; ответ — реплаем
/new Названиесоздать эпик (без названия бот спросит следующим сообщением)
/c 49 тексткомментарий к эпику (без текста — спросит следующим сообщением)
/find словопоиск по трекеру (по эпикам)
/pingпроверка связи: «pong» и время сервера
/cancelотменить ожидание ответа
/helpсправка

Подтверждения и кнопки

- «✅ Принять (done)» — ставит done от имени пользователя; - «↩️ Вернуть в работу» — бот спрашивает причину, следующее сообщение переводит эпик ready → on work и записывает причину комментарием; - «📋 Карточка».

Устойчивость

Режим «Чат»

Над левой колонкой — переключатель «Трекер / Чат». Режим определяется адресом: #chat, #chat-12 — чат, всё остальное — трекер; последний режим запоминается в браузере.

Как отвечает Claude (мост)

dispatcher/chat_bridge.py (только стандартная библиотека Python) запускается рядом с Claude Code — на VPS или на ПК — и работает ключом роли claude:

  1. Раз в poll_seconds — GET /chats/pending (обычный HTTP-запрос, модель не тратится).
  2. Забирает сообщения (take, повтор — 409, значит другой проход уже забрал), склеивает их в один промпт и запускает claude -p --output-format stream-json --verbose --include-partial-messages --resume <session_id>; промпт передаётся через stdin (кириллица в аргументах Windows ломается), session_id сохраняется в чате — разговор продолжается между запусками.
  3. События потока превращает в сообщения: дельты текста дописываются (append) в одно сообщение assistant, вызовы инструментов — отдельные строки tool с результатом, ошибки — строка system; в конце чат снова idle.
  4. Разрешения. Инструмент, на который не хватило прав (permission_denials), показывается карточкой; после «Разрешить» мост запоминает правило для чата и повторяет запуск с --allowedTools, после «Отклонить» сообщает Claude, что действие запрещено.
  5. Предохранители: max_parallel, max_runs_per_hour, run_timeout_seconds, список allowed_tools и permission_mode в настройках (chat_bridge.config.example.json).

Мост проверяется без сети и без настоящего Claude: python dispatcher/test_chat_bridge.py (подменный трекер в памяти и fake_claude.py).

Решения и почему

Режим «Секреты»

Третья кнопка переключателя — «Секреты» (адрес #secrets). Раздел открывается только после входа по паролю и ввода PIN-кода (задаётся в config.php, ключ secrets_pin); после ввода доступ живёт 15 минут, потом интерфейс сам возвращается к вводу PIN. Записи сгруппированы по проектам: аннотация (что за реквизиты) и значение; значение скрыто, пока не нажато «показать»; есть «копировать», «изменить», «удалить», «+ Запись» и «Заблокировать». Claude (ключ claude) не имеет доступа ни к одному маршруту раздела; данные лежат в таблице secrets и не участвуют в поиске, журнале и уведомлениях. Хранятся открытым текстом в базе хостинга (как и конфиг с паролями) — защита держится на входе по паролю, PIN-коде и ограничении по ролям.

Диспетчер для Claude

dispatcher/dispatcher.py (только стандартная библиотека Python) работает рядом с Claude Code:

  1. Раз в минуту GET inbox — обычный HTTP-запрос, модель не тратится.
  2. Если есть новые записи пользователя — группирует по эпикам и запускает claude -p "<что появилось>" --output-format json в папке проекта (workdir эпика), продолжая прошлую сессию этого эпика (--resume <session_id>); id сессий хранит в state.json.
  3. После успешного запуска отмечает записи прочитанными (inbox/ack). Если Claude завершился с ошибкой — записи остаются непрочитанными, а пользователю уходит уведомление в Telegram.
  4. Предохранители: не более max_runs_per_hour запусков в час, таймаут запуска, режим --dry-run.

Потоки данных

Ключевые решения и почему

Установка

  1. Создать БД MySQL (отдельную или общую; таблицы с префиксом trk_).
  2. Скопировать config.example.php в config.php, вписать доступ к БД, php tools/make_secrets.php "пароль" — сгенерирует хэш пароля и ключи (user, claude, install_key, webhook_secret), вписать их, указать токен бота и allowed_chat_ids.
  3. Залить файлы, открыть install.php?key=<install_key> (или php install.php) — создаются таблицы.
  4. (Если нужен перенос старых данных) python tools/export_sqlite.py → php tools/import.php tools/dump.json --replace.
  5. Зарегистрировать приёмник: php bot/set_webhook.php set https://домен/путь/bot/webhook.php и … commands. Проверить: … info.
  6. На VPS с Claude: dispatcher/config.json (по образцу), затем python dispatcher/dispatcher.py (лучше как службу / в tmux).

Локально (Windows): стенд MariaDB D:\CLAUDE\stands\tracker-web\start.bat (порт 3370), сайт — php -S 127.0.0.1:8100 router.php.

Тесты

Боевая площадка

Хостинг Timeweb (PHP 8.2, Apache с mod_rewrite, MySQL), сайт it-here-friend.ru. Раскладка папок: корень — лендинг, task_tracker/ — этот трекер (https://it-here-friend.ru/task_tracker/), vml_claude_bot/ — сервис отправки сообщений бота (send.php, отдельный проект VM_CLAUDE_BOT). FTP-пользователь ограничен папкой сайта: выше public_html подняться не может, каждый проект пишет только в свою подпапку. Заливка — python tools/deploy_ftp.py check|plan|push|config|verify (доступы лежат вне проекта, в D:\CLAUDE\deploy; перед перезаписью файла старая версия сохраняется, после заливки ставится git-тег deployed). Приёмник Telegram на этой площадке: https://it-here-friend.ru/task_tracker/bot/webhook.php; он размещён и проверен, но в Telegram не зарегистрирован, пока вы не скажете (регистрация переключит бота с опроса на вебхук).

Что заливать на сервер

Заливать: index.php, api.php, file.php, install.php, .htaccess, lib/, static/, bot/, docs/, files/.htaccess, config.php (создаётся на сервере, не перезаписывать). Не заливать: tests/, tools/ (кроме одноразовых), dispatcher/ (она едет на VPS), router.php, .git, config.example.php — по желанию.