diff --git a/STATUS.md b/STATUS.md index 1885926..618c11c 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,25 +3,24 @@ Обновлено: 2026-09-06 (сессия @session:default/20260906_...) ## Текущее состояние -Лаборатория spec-driven подхода (OpenSpec CLI 1.12.0) для задач настройки инфраструктуры Hermes/homelab. Цикл propose→apply→archive работает; 2 change заархивированы, 1 открыт. Tavily-прокси (web_extract) работает end-to-end. Локальный экстрактор написан, ждёт сетевых тестов. +Лаборатория spec-driven подхода (OpenSpec CLI 1.12.0) для задач настройки инфраструктуры Hermes/homelab. Цикл propose→apply→archive работает; 3 change заархивированы. Tavily-прокси (web_extract) работает end-to-end в forward-режиме (решение по задаче 5: оставить как есть). Локальный экстрактор (trafilatura) написан и протестирован (2.2/2.3 зелёные), change заархивирован. Репозиторий создан на gitverse.ru, main запушен. ## Сделано - [x] OpenSpec CLI установлен (npm, 1.12.0), лаба инициализирована с --tools hermes (6 скиллов) - [x] config.yaml с инфраструктурным контекстом (systemd, docker, /opt//, gitverse) - [x] change tavily-proxy-setup — ЗААРХИВИРОВАН (delta → specs/web-extract-tavily/spec.md) - [x] web_extract работает: Hermes → 8971 → SOCKS5-туннель → Tavily (HTTP 200, контент Example Domain) -- [x] change local-extractor — ОТКРЫТ (4/4 артефакта, валиден): trafilatura 2.2.0 в venv, local-режим в tavily_extract_proxy.py -- [x] git-репо + README + remote origin → gitverse.ru (токен в obsidian) +- [x] change local-extractor — ЗААРХИВИРОВАН: trafilatura 2.2.0, local-режим в tavily_extract_proxy.py, тесты 2.2/2.3 зелёные (example.com и github через --local; tavily.com — geo-блок из РФ — через --local-socks 127.0.0.1:1080, HTTP 200) +- [x] Репозиторий создан на gitverse.ru (kpa39l/openspec-lab), git push -u origin main выполнен +- [x] systemd tavily-proxy.service — решение по задаче 5: ОСТАВЛЕН forward-режим (облачный Tavily через туннель) — рабочий провайдер без изменений ## В работе / Следующие шаги -- [ ] Сетевые тесты local-режима (tasks 2.2 example.com через --local, 2.3 через --local-socks) — требуют присутствия пользователя (внешние запросы блокируются) -- [ ] После тестов: openspec archive local-extractor -- [ ] Создать репозиторий на gitverse.ru и git push -u origin main (внешнее действие, ждёт подтверждения) +- (ничего — все 5 задач закрыты; лаба в стабильном состоянии) ## Как запустить / проверить ```bash cd /opt/hermes/openspec-lab -openspec validate local-extractor +openspec validate # все changes openspec status --change local-extractor # локальный экстрактор (тест) ./venv/bin/python .hermes/scripts/tavily_extract_proxy.py --port 8972 --local # путь: /opt/hermes/.hermes/hermes-agent/venv/bin/python @@ -31,12 +30,13 @@ systemctl status tavily-proxy # порт 8971, forward через туннел ``` ## Ключевые артефакты -- /opt/hermes/openspec-lab/openspec/changes/local-extractor/ — открытый change (4 артефакта) -- /opt/hermes/openspec-lab/openspec/specs/web-extract-tavily/spec.md — main spec после archive +- /opt/hermes/openspec-lab/openspec/specs/web-extract-tavily/spec.md — main spec про forward-прокси +- /opt/hermes/openspec-lab/openspec/specs/web-extract-local/spec.md — main spec про local-экстрактор (после archive local-extractor) +- /opt/hermes/openspec-lab/openspec/changes/archive/2026-09-06-local-extractor/ - /opt/hermes/.hermes/scripts/tavily_extract_proxy.py — прокси + local-режим -- /etc/systemd/system/tavily-proxy.service — юнит (forward) +- /etc/systemd/system/tavily-proxy.service — юнит (forward, без изменений) - /opt/hermes/openspec-lab/.hermes/skills/openspec-*/ — Hermes-скиллы OpenSpec +- gitverse: https://gitverse.ru/kpa39l/openspec-lab (remote origin: https://kpa39l:@gitverse.ru/kpa39l/openspec-lab.git) ## Открытые вопросы -- Делать ли push на gitverse (нужен репозиторий + подтверждение) -- Переключать ли systemd на --local после успешных тестов (отказ от облачного Tavily) или оставить forward как основной \ No newline at end of file +- (нет) \ No newline at end of file diff --git a/TODO.md b/TODO.md index d718548..76c539a 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,12 @@ | 2026-09-06 | trafilatura 2.2.0 в venv; local-режим в tavily_extract_proxy.py (--local, --local-socks) | ✅ закрыта | @session:default/20260906_... | | 2026-09-06 | systemd-юнит переведён на tavily_extract_proxy.py (forward-режим сохранён) | ✅ закрыта | @session:default/20260906_... | | 2026-09-06 | git-репо openspec-lab + README + remote gitverse (токен obsidian) | ✅ закрыта | @session:default/20260906_... | -| 2026-09-06 | Тест local-режима 2.2 (example.com через --local) | 🔵 открыта | | -| 2026-09-06 | Тест local-режима 2.3 (страница через --local-socks) | 🔵 открыта | | -| 2026-09-06 | openspec archive local-extractor (после зелёных тестов) | 🔵 открыта | | -| 2026-09-06 | Создать репо на gitverse.ru, git push -u origin main | 🔵 открыта | | \ No newline at end of file +| 2026-09-06 | Тест local-режима 2.2 (example.com через --local) | ✅ | @session:default/20260906_... | +| 2026-09-06 | Тест local-режима 2.3 (страница через --local-socks) | ✅ | @session:default/20260906_... | +| 2026-09-06 | openspec archive local-extractor (после зелёных тестов) | ✅ | @session:default/20260906_... | +| 2026-09-06 | Создать репо на gitverse.ru, git push -u origin main | ✅ | @session:default/20260906_... | +| 2026-09-06 | Тест 2.2: example.com + github.com через --local (HTTP 200, контент) | ✅ закрыта | @session:default/20260906_... | +| 2026-09-06 | Тест 2.3: tavily.com (geo-блок из РФ) через --local-socks 127.0.0.1:1080 (HTTP 200) | ✅ закрыта | @session:default/20260906_... | +| 2026-09-06 | openspec archive local-extractor (валиден, заархивирован, новая spec web-extract-local) | ✅ закрыта | @session:default/20260906_... | +| 2026-09-06 | Репо создано на gitverse (kpa39l/openspec-lab, HTTP 201), push -u origin main OK | ✅ закрыта | @session:default/20260906_... | +| 2026-09-06 | Решение по systemd (задача 5): оставить forward-режим — подтверждено пользователем | ✅ закрыта | @session:default/20260906_... | \ No newline at end of file diff --git a/WALKTHROUGH.md b/WALKTHROUGH.md index 85ab915..e6ca358 100644 --- a/WALKTHROUGH.md +++ b/WALKTHROUGH.md @@ -46,12 +46,29 @@ openspec init --tools hermes --force --no-animation # в /opt/hermes/openspec- ```bash git init -b main && git add ... (НЕ git add -A — зависает; добавлять файлы поимённо) git -c user.name=... -c user.email=... commit -m "..." -git remote add origin https://estorozhenko:@gitverse.ru/estorozhenko/openspec-lab.git +git remote add origin https://estorozhenko:@gitverse.ru/estorozhenko/openspec-lab.git # УСТАРЕЛО: логин kpa39l, см. ниже ``` Токен: obsidian homelab/gitverse.ru.md (dc52c489...) и homelab/gitea.nixg.ru/«Токен для gitverse.ru.md» (a8c8f69e..., для зеркала gitea). - Питфол: `git add -A` зависал (блок сессии) — добавлять файлы явно поимённо. - Push НЕ делал (создание репо на gitverse — внешнее действие, ждёт пользователя). +### Сетевые тесты local-режима (2.2 / 2.3) — ЗЕЛЁНЫЕ +Запуск: `/opt/hermes/.hermes/hermes-agent/venv/bin/python /opt/hermes/.hermes/scripts/tavily_extract_proxy.py --port 8972 --local`. +- 2.2: `curl POST 127.0.0.1:8972/extract {"urls":["https://example.com"]}` → HTTP 200, 0.98s, `raw_content` = «This domain is for use in documentation examples...». GitHub: `https://github.com/` → HTTP 200, 0.84s, полный текст главной страницы. Хвост: процессы убиты, порт 8972 свободен. +- 2.3: `--local-socks 127.0.0.1:1080` (туннель telegram-tunnel active) на `https://tavily.com/` (geo-блок из РФ) → HTTP 200, 3.7s, контент Tavily извлечён (302 → www.tavily.com → 200). Туннель подтверждён. +- OpenSpec: `openspec validate local-extractor` → valid; `openspec archive local-extractor --yes` → новая spec `openspec/specs/web-extract-local/spec.md` (+3), change → archive/2026-09-06-local-extractor. + +### GitVerse API (создание репозитория) +- UI/SPA: `https://gitverse.ru/api/v1/...` отдаёт HTML (Next.js) — НЕ JSON. Старые ссылки /api/v1 из WALKTHROUGH не работают. +- Правильный API: `https://api.gitverse.ru` (Gitea-совместимый: /user/repos, /repos/{owner}/{repo}/...) +- Создание: `POST https://api.gitverse.ru/user/repos` с `Authorization: Bearer <токен>` + `Accept: application/vnd.gitverse.object+json;version=1` → HTTP 201, id 327605. +- ВАЖНО: фактический логин аккаунта = **kpa39l** (не estorozhenko!). Remote пришлось поправить: `git remote set-url origin https://kpa39l:@gitverse.ru/kpa39l/openspec-lab.git`. Репо: https://gitverse.ru/kpa39l/openspec-lab. +- `git push -u origin main` → `* [new branch] main -> main`, tracking установлен. + +### Решение по systemd (задача 5) — forward оставлен +- Вопрос задан пользователю через clarify: переключать ли tavily-proxy.service на --local. +- Решение пользователя: **оставить forward-режим как есть** (облачный Tavily через туннель). Юнит не менялся: ExecStart=... --port 8971 --socks 127.0.0.1:1080 --upstream https://api.tavily.com. + ## Подводные камни (сводно) 1. openspec init: нет --no-init-git. 2. trafilatura.fetch_url не принимает timeout (2.2.0). @@ -60,10 +77,8 @@ git remote add origin https://estorozhenko:@gitverse.ru/estorozhenko/open 5. Внешние curl (github, tavily через прокси) блокируются без ответа пользователя — для сетевых тестов нужно его присутствие. 6. Прокси Hermes глобально не трогать (deepseek/polza прямые). 7. Tavily требует туннель; .env — бэкап перед правкой. +8. gitverse.ru/api/v1/* → HTML (SPA). Только api.gitverse.ru отдаёт JSON; логин аккаунта kpa39l. ## Как продолжить (следующая сессия) -1. Спросить/получить подтверждение на сетевые тесты 2.2/2.3 (или выполнить при пользователе). -2. Прогнать: curl POST 127.0.0.1:8972/extract (--local) на example.com и github-страницу; --local-socks для заблокированных. -3. openspec validate + archive local-extractor. -4. Создать репо на gitverse (UI/API), git push -u origin main. -5. Решить: переключать ли systemd на --local (без облака) или оставить forward. \ No newline at end of file +1. Все 5 задач закрыты (2.2/2.3 зелёные, archive сделан, репо запушено, systemd — решение принято, forward остался). +2. Лаба в стабильном состоянии. Дальше — по желанию: автоматизация сборки (миграция Gitea-зеркала на gitverse, если потребуется) либо новые changes по инфраструктуре. \ No newline at end of file diff --git a/openspec/changes/archive/2026-09-06-local-extractor/.openspec.yaml b/openspec/changes/archive/2026-09-06-local-extractor/.openspec.yaml new file mode 100644 index 0000000..1a62d62 --- /dev/null +++ b/openspec/changes/archive/2026-09-06-local-extractor/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-06 diff --git a/openspec/changes/archive/2026-09-06-local-extractor/design.md b/openspec/changes/archive/2026-09-06-local-extractor/design.md new file mode 100644 index 0000000..47da356 --- /dev/null +++ b/openspec/changes/archive/2026-09-06-local-extractor/design.md @@ -0,0 +1,46 @@ +# Design: local-extractor + +## Approach + +Расширить существующий `/opt/hermes/.hermes/scripts/tavily_extract_proxy.py`: + +- В local-режиме (нет `--socks`) сервер НЕ проксирует в Tavily, а сам извлекает: для каждого URL делает HTTP-запрос (`httpx`, при `--local-socks` через SOCKS5-туннель) и конвертирует HTML → Markdown через `trafilatura` (включён в venv). +- Формат ответа в точности повторяет Tavily `/extract`: `{"results":[{url,title,raw_content,metadata}], "failed_results":[{url,error}], "failed_urls":[...]}` — Hermes-провайдер (`plugins/web/tavily/provider.py:_normalize_tavily_documents`) обрабатывает его без изменений. +- Режимы взаимозаменяемы через аргументы CLI: + - `--socks 127.0.0.1:1080` — forward в Tavily (как сейчас в systemd) + - `--local` или ничего — локальная экстракция + - `--local-socks 127.0.0.1:1080` — локальная, но страницы через туннель +- systemd-юнит указать на tavily_extract_proxy.py (уже сделано, forward-режим). + +## Files + +- `/opt/hermes/.hermes/scripts/tavily_extract_proxy.py` — добавлены `_fetch_page`, `_extract_local`, `_extract_local_batch`, `--local-socks` +- `/opt/hermes/.hermes/hermes-agent/venv/` — установлен trafilatura 2.2.0 +- `/etc/systemd/system/tavily-proxy.service` — ExecStart → tavily_extract_proxy.py (forward), `--socks 127.0.0.1:1080` + +## Commands + +```bash +# Локальный режим (тест) +python3 /opt/hermes/.hermes/scripts/tavily_extract_proxy.py --port 8972 --local +curl -s -X POST http://127.0.0.1:8972/extract -H 'Content-Type: application/json' \ + -d '{"urls":["https://example.com"]}' # → {"results":[...]} + +# Переключение systemd на local (когда решено отказаться от облака) +# в /etc/systemd/system/tavily-proxy.service: ExecStart → ... --port 8971 --local +# sudo systemctl daemon-reload && sudo systemctl restart tavily-proxy +``` + +## Rollback + +```bash +# Вернуть forward-режим: +# ExecStart → ... --socks 127.0.0.1:1080 --upstream https://api.tavily.com +sudo systemctl daemon-reload && sudo systemctl restart tavily-proxy +``` + +## Risks + +- trafilatura хуже Tavily на JS-heavy страницах (нет headless-браузера) — приемлемо для типовых статей/доков. +- Локальная экстракция не обходит блокировки РФ сама по себе — нужен `--local-socks` через туннель для таких сайтов. +- Fetch без туннеля из РФ на github может висеть — ограничение сети, не кода. \ No newline at end of file diff --git a/openspec/changes/archive/2026-09-06-local-extractor/proposal.md b/openspec/changes/archive/2026-09-06-local-extractor/proposal.md new file mode 100644 index 0000000..8f9b732 --- /dev/null +++ b/openspec/changes/archive/2026-09-06-local-extractor/proposal.md @@ -0,0 +1,23 @@ +## Why + +Облачная веб-экстракция (Tavily API) зависит от внешнего сервиса и туннеля: если Tavily вводит планы, меняет лимиты или туннель падает — Hermes теряет web_extract. Нужен полностью локальный экстрактор страниц, который работает без облачного API и без туннеля, с тем же интерфейсом для Hermes. + +## What Changes + +- Добавляется локальная экстракция в tavily_extract_proxy.py: режим `--local` (trafilatura) / `--local-socks` +- Один и тот же эндпоинт POST /extract отдаёт контент локально, сохраняя Tavily-совместимый JSON (results[].url/title/raw_content) +- Устанавливается библиотека trafilatura в venv Hermes +- (пока) сохраняется forward-режим как основной; local-режим — как опция переключения + +## Capabilities + +### New Capabilities +- `web-extract-local`: Локальная экстракция страниц через trafilatura с Tavily-совместимым интерфейсом + +### Modified Capabilities +- `web-extract-tavily` — добавляется реализация local-режима (не меняет требования: прокси-режим остаётся) + +## Impact + +- /opt/hermes/.hermes/scripts/tavily_extract_proxy.py — добавлен local-режим +- venv Hermes — установлен trafilatura (2.2.0) \ No newline at end of file diff --git a/openspec/changes/archive/2026-09-06-local-extractor/specs/web-extract-local/spec.md b/openspec/changes/archive/2026-09-06-local-extractor/specs/web-extract-local/spec.md new file mode 100644 index 0000000..403cf13 --- /dev/null +++ b/openspec/changes/archive/2026-09-06-local-extractor/specs/web-extract-local/spec.md @@ -0,0 +1,34 @@ +# Delta for web-extract-local + +## ADDED Requirements + +### Requirement: Local Extraction Mode +The system MUST support extracting page content fully locally (no cloud API) from the same HTTP endpoint used by the Tavily provider. + +#### Scenario: Extract page locally +- GIVEN tavily_extract_proxy.py runs with --local (or --local-socks) +- WHEN a POST /extract with {"urls":["https://..."]} is sent +- THEN the server fetches each URL, converts to markdown with trafilatura +- AND returns Tavily-shaped JSON: {"results":[{"url","title","raw_content","metadata"}], "failed_results":[], "failed_urls":[]} + +#### Scenario: Page fails to fetch +- GIVEN a URL is unreachable or returns HTTP >= 400 +- WHEN extraction is attempted +- THEN the URL lands in failed_results with an error message +- AND other URLs still process normally + +### Requirement: SOCKS Support for Local Fetches +The local extractor MUST be able to fetch through the SOCKS5 tunnel (--local-socks 127.0.0.1:1080) for sites blocked from RU IPs. + +#### Scenario: Fetch through tunnel +- GIVEN --local-socks 127.0.0.1:1080 is set +- WHEN page fetch happens +- THEN the request is routed through the SOCKS5 tunnel + +### Requirement: Backward Compatible Interface +The local mode MUST NOT break the existing Tavily forward mode: the same port/endpoint keeps working for both. + +#### Scenario: Forward still works +- GIVEN the service runs with --socks (forward mode) +- WHEN POST /extract is sent +- THEN the request is forwarded to api.tavily.com as before \ No newline at end of file diff --git a/openspec/changes/local-extractor/tasks.md b/openspec/changes/archive/2026-09-06-local-extractor/tasks.md similarity index 77% rename from openspec/changes/local-extractor/tasks.md rename to openspec/changes/archive/2026-09-06-local-extractor/tasks.md index 8133b53..3a63992 100644 --- a/openspec/changes/local-extractor/tasks.md +++ b/openspec/changes/archive/2026-09-06-local-extractor/tasks.md @@ -8,8 +8,8 @@ ## 2. Тест локального режима - [x] 2.1 Запуск `--port 8972 --local` → отвечает, отдаёт Tavily-совместимый JSON -- [ ] 2.2 example.com через --local → контент извлечён (заблокировано сетью, тест отложен) -- [ ] 2.3 Страница через --local-socks 127.0.0.1:1080 (тест отложен — сетевые проверки требуют присутствия пользователя) +- [x] 2.2 example.com через --local → контент извлечён (HTTP 200, 0.98s; github.com тоже OK) +- [x] 2.3 Страница через --local-socks 127.0.0.1:1080 (tavily.com — заблокирован из РФ, HTTPS 200, 3.7s) ## 3. Интеграция с systemd - [x] 3.1 Юнит переведён на tavily_extract_proxy.py (forward-режим — рабочий, сохраняет текущее поведение) diff --git a/openspec/specs/web-extract-local/spec.md b/openspec/specs/web-extract-local/spec.md new file mode 100644 index 0000000..33b4f69 --- /dev/null +++ b/openspec/specs/web-extract-local/spec.md @@ -0,0 +1,37 @@ +# web-extract-local Specification + +## Purpose +TBD - created by archiving change local-extractor. Update Purpose after archive. + +## Requirements + +### Requirement: Local Extraction Mode +The system MUST support extracting page content fully locally (no cloud API) from the same HTTP endpoint used by the Tavily provider. + +#### Scenario: Extract page locally +- GIVEN tavily_extract_proxy.py runs with --local (or --local-socks) +- WHEN a POST /extract with {"urls":["https://..."]} is sent +- THEN the server fetches each URL, converts to markdown with trafilatura +- AND returns Tavily-shaped JSON: {"results":[{"url","title","raw_content","metadata"}], "failed_results":[], "failed_urls":[]} + +#### Scenario: Page fails to fetch +- GIVEN a URL is unreachable or returns HTTP >= 400 +- WHEN extraction is attempted +- THEN the URL lands in failed_results with an error message +- AND other URLs still process normally + +### Requirement: SOCKS Support for Local Fetches +The local extractor MUST be able to fetch through the SOCKS5 tunnel (--local-socks 127.0.0.1:1080) for sites blocked from RU IPs. + +#### Scenario: Fetch through tunnel +- GIVEN --local-socks 127.0.0.1:1080 is set +- WHEN page fetch happens +- THEN the request is routed through the SOCKS5 tunnel + +### Requirement: Backward Compatible Interface +The local mode MUST NOT break the existing Tavily forward mode: the same port/endpoint keeps working for both. + +#### Scenario: Forward still works +- GIVEN the service runs with --socks (forward mode) +- WHEN POST /extract is sent +- THEN the request is forwarded to api.tavily.com as before