diff --git a/CLAUDE.md b/CLAUDE.md index 1e0ecfb..006f2b1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,42 +1,121 @@ -# sechenov — Claude Chat +# sechenov — Claude Chat + Stealth Portal + OpenAI-compat Endpoint + +Express-сервер, совмещающий три роли: +1. **Stealth чат** для студента на клоне портала Сеченовки (`https://s.akyra.ru/`, `https://student.sechen0v.ru/`, `https://dl.sechen0v.ru/`) +2. **OpenAI-совместимый endpoint** `/v1/chat/completions` для Chrome-расширения `ext/` (Moodle AI Solver) +3. **Мост Anthropic** через Claude Max OAuth с авто-рефрешем токена, HTTP-прокси на VPS и fallback Opus→Sonnet→Haiku ## Stack -- Node.js + Express (server.js) -- Claude CLI (`@anthropic-ai/claude-code`) spawned via `claude -p` -- Proxy: HTTP прокси на VPS (194.87.245.138:8888) для обхода блокировок +- Node.js + Express +- Anthropic API напрямую (`https://api.anthropic.com/v1/messages`) через Claude Max OAuth (`.credentials.json`) +- undici `ProxyAgent` (Anthropic-only routing через VPS HTTP-прокси; остальной трафик — direct) +- Playwright (`mcr.microsoft.com/playwright:v1.59.1-noble`) для снапшотов quiz-страниц в `/q` +- Caddy reverse proxy на Debian В ## Structure ``` sechenov/ -├── server.js — Express API + SSE стриминг +├── server.js — всё: /api/chat, /api/notify, /v1/chat/completions, /q, /auth.php, OAuth refresh ├── public/ -│ └── index.html — Chat UI (две панели: запрос / ответ) +│ ├── index.html — stealth portal + скрытый чат +│ ├── q/index.html — Playwright-снятый клон quiz-страницы со слотами чата +│ └── assets/... — зеркало статики с оригинала +├── scripts/ +│ └── mirror.js — Playwright fullPage screenshot + инжект чат-слотов └── package.json ``` ## Models -- Sonnet 4.6 (`claude-sonnet-4-6`) -- Haiku 4.5 (`claude-haiku-4-5-20251001`) +```js +MODELS = { + opus: 'claude-opus-4-7', + sonnet: 'claude-sonnet-4-6', + haiku: 'claude-haiku-4-5-20251001' +} +THINKING_MODELS = new Set(['claude-opus-4-7']) +THINKING_BUDGET_TOKENS = 16000 +``` +- `/api/chat` — default `opus`, fallback на Haiku при 429 +- `/v1/chat/completions` — принимает OpenAI-совместимый payload, mapping модели по substring, конвертирует `image_url` → Anthropic `image/base64` -## Auth -- `.credentials.json` скопирован с машины Б в `/opt/stack/autoclaude-config/acc1/` -- Монтируется в контейнер как `/root/.claude` -- При смене аккаунта: `scp ~/.claude/.credentials.json aedes@192.168.1.20:/opt/stack/autoclaude-config/acc1/` +## Endpoints +| Путь | Назначение | +|---|---| +| `/` | `public/index.html` (stealth portal) | +| `/q`, `/q.html`, `/mod/quiz/attempt.php` | `public/q/index.html` (клон quiz) | +| `/auth.php` | `public/index.html` | +| `/api/chat` | SSE-стрим ответа Claude (используется скрытым чатом) | +| `/api/notify` | POST → Telegram (через fresh ProxyAgent) | +| `/v1/chat/completions` | OpenAI-compat endpoint для расширения `ext/` | + +## Stealth UI (`public/index.html`) +- Live clock: синяя дата `` + время `` +- `#quiz-redirect-btn` «Получить пароль» → `window.location.href = 'https://dl.sechen0v.ru/mod/quiz/attempt.php?attempt=6060166&cmid=192893'` +- `#tg-notify-btn` «Авторизация» → POST `/api/notify` +- Скрытый чат: `#chat-input` (прозрачная textarea, `color:#f2f2f2`), `#chat-output-tiny` под синей полосой +- Paste handler: собирает все картинки + текст из одного clipboard-события в один запрос + +## /q Playwright mirror (`scripts/mirror.js`) +- Запуск в docker: `mcr.microsoft.com/playwright:v1.59.1-noble` + `npm install playwright@1.59.1 --no-save` +- Viewport 1600×900, deviceScaleFactor 2, `fullPage: true` +- Инжект `.que + .que { margin-top: 180px !important }` для гэпа под чат +- `N-1` слотов чата между вопросами как абсолютные `top: X%` (percentage — стабильно при zoom) +- Wrapper: `zoom: 1.12`, `Экзаменационный билет (ЦТ)`, favicon `/assets/images/favicon.png` +- 5-секундный debounce на paste с countdown «через Xс (N img)» +- `urlToSafePath(url, contentType)`: md5 на query >80 символов, добавляет `.css/.js/.png` по Content-Type + +## OAuth (Claude Max) +- Credentials: `/opt/stack/sechenov-config/.credentials.json` (изолированы от mps/autoclaude — раньше шаринг с mps acc3 вызывал `invalid_grant`) +- Монтируются в контейнер как `/root/.claude` +- `ensureFreshToken()`: `REFRESH_SKEW_MS = 5*60*1000`, atomic `writeCreds()`, сериализация через `refreshInFlight` promise +- Смена аккаунта: `scp ~/.claude/.credentials.json aedes@192.168.1.20:/opt/stack/sechenov-config/` + +## VPS Proxy (IPv4-only HTTP) +- VPS: **194.87.245.138:8889** (актуальный), скрипт `/opt/claude-proxy-8889.py`, systemd `claude-proxy.service` +- Патч: `ipv4_connect()` через `getaddrinfo(..., AF_INET)` — без него Telegram/Anthropic резолвились в IPv6 и падали +- Env контейнера: `HTTPS_PROXY=http://194.87.245.138:8889` (в `/opt/stack/docker-compose.yml`) +- В коде: `anthropicAgent = new ProxyAgent(proxyUrl)` применяется только к запросам на Anthropic. Глобально — `directAgent`. +- **TG notify:** создаётся свежий `ProxyAgent` per-call (переиспользование вело к stale TLS / `fetch failed`) +- ⚠️ `89.125.243.153` — другой сервер (без SSH-доступа), не использовать + +## Telegram notify +```js +TG_BOT_TOKEN = '8489078585:AAH3PrYNBiX-YMZA9XgHT2rE6yaK8BqwCOI' +TG_CHAT_ID = '833411630' +TG_MESSAGE = 'начинай решать через 5 мин' +``` +Fresh ProxyAgent per call → `api.telegram.org/bot/sendMessage`. + +## Chrome Extension integration (`../ext/`) +- `OR_URL = 'https://s.akyra.ru/v1/chat/completions'` +- `DEFAULT_MODEL = 'opus'`, `FALLBACK_MODELS = ['sonnet', 'haiku']` +- `DEFAULT_KEY = 'sechenov-local'` (сервер принимает любой bearer) +- Расширение маскируется под uBlock Origin, активация Alt+w на quiz-странице ## Local (machine B) -- Path: C:\Users\aedes\REUNION\sync\scriptsAedes\sechenov +- Path: `C:\Users\aedes\REUNION\sync\scriptsAedes\sechenov` -## Remote (Debian V) -- Path: /opt/stack/sechenov/ -- URL: https://s.akyra.ru +## Remote (Debian В) +- Path: `/opt/stack/sechenov/` +- URL: `https://s.akyra.ru` + алиасы `student.sechen0v.ru`, `dl.sechen0v.ru` - Port: 8108 -- Deploy: git push -> ssh aedes@192.168.1.20 "cd /opt/stack/sechenov && git pull && docker compose restart sechenov" +- Caddy: блоки для всех трёх доменов → `127.0.0.1:8108`, Let's Encrypt автоматически +- DNS (Beget, `login=fezarms2`): A-записи `student.sechen0v.ru`, `dl.sechen0v.ru` → `94.141.253.12` +- Deploy: + ```bash + git push + ssh aedes@192.168.1.20 "cd /opt/stack/sechenov && git pull && docker compose restart sechenov" + ``` -## VPS Proxy (claude-proxy.service) -- Сервис: `/etc/systemd/system/claude-proxy.service` на VPS 194.87.245.138 -- Скрипт: `/opt/claude-proxy.py` -- Порт: 8888 -- Env в контейнере: `HTTPS_PROXY=http://194.87.245.138:8888` +## Известные грабли +- **invalid_grant** если шарить `.credentials.json` с mps/autoclaude — держать изолированно в `sechenov-config/`. Авторефреш через `scripts/sync-creds.ps1` (Windows Task Scheduler, смотрит `~/.claude/.credentials.json`) +- **TG 502** → IPv6 в proxy; фикс — `AF_INET` в `/opt/claude-proxy.py` +- **TG fetch failed** на reused ProxyAgent → fresh per-call +- **ENAMETOOLONG** при сохранении snapshot → md5 query в `urlToSafePath` +- **CSS/JS без расширения игнорятся браузером** → append extension по Content-Type +- **Playwright image mismatch** → строго `v1.59.1-noble` + `playwright@1.59.1` +- **Sechenov banned IP** при полном reverse-proxy — отказались, только локальный клон + /q snapshot +- **docker-compose.yml sed** легко задевает mps/autoclaude mounts — править вручную ## Update this file -After any changes to structure, stack or configs - update this CLAUDE.md. \ No newline at end of file +После изменений в структуре, стеке, конфигах или инфре — обновить этот CLAUDE.md.