mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
docs: итоги сессии 2026-09-11 — STATUS обновлён, TODO/WALKTHROUGH созданы
- STATUS: дата 2026-09-11, реальные метрики (4884 письма, 81 контакт), cron id реальные (5f2305b2bbf8/ea0fd1ab4f93), блок портфеля веб-UI - TODO.md: журнал задач (2026-07 → 2026-09-11) - WALKTHROUGH.md: капитанский журнал (Фаза 1.7, Nylas, ФС vs Maildir, Radicale развёрнут, блокер с коллекциями) - PLAN_WEBUI.md: план портфеля (был untracked)
This commit is contained in:
+143
@@ -0,0 +1,143 @@
|
||||
# Email Assistant — Расширение: Web UI + Календарь/Задачи + Анализ Maildir
|
||||
|
||||
**Дата:** 2026-09-11
|
||||
**Тип:** Планирование портфеля задач (OpenSpec changes)
|
||||
**Приоритет:** Задачи 4 → 2 → 3 → 1 → 6 → 7
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
Существующий проект: `/opt/hermes/email-assistant/`
|
||||
- Архив писем: `/opt/hermes/email/` (email.md с YAML-frontmatter: id, folder, subject,
|
||||
from, to, date, flags, Message-ID, References, Content-Type + сырое тело)
|
||||
- Индексация: SQLite FTS5 (`mail_index.db`, ~5.4 MB, 2652 письма)
|
||||
- LLM: Qwen3:8b через Ollama localhost:11434 (без облака)
|
||||
- Стэк: Himalaya CLI → Python → SQLite → Ollama → Yandex Disk
|
||||
- SMTP не настроен (только чтение)
|
||||
|
||||
Календаря/трекера задач **нет** (проверено: порты 5232/8008/8080 не заняты —
|
||||
ни radicale, ни vikunja, ни leantime).
|
||||
|
||||
---
|
||||
|
||||
## Задача 4 (приоритет №1): Анализ «Файловая система vs Maildir» для хранения писем
|
||||
|
||||
**Пользователь хочет:** хранить данные в ФС, чтобы использовать локальную
|
||||
нейросеть как инструмент в обычных скриптах, без облака и трат.
|
||||
|
||||
**Что сделать:**
|
||||
1. Сравнить текущий подход (email.md в YYYY/MM/UID/ + SQLite FTS5-индекс) с Maildir
|
||||
2. Оценить: производительность, инкрементальность, устойчивость, совместимость,
|
||||
возможность LLM-анализа (grep/find/без БД)
|
||||
3. Вывод: остаться на текущем или мигрировать (с обоснованием)
|
||||
|
||||
**Критерии готовности:**
|
||||
- [ ] Документ `STORAGE_ANALYSIS.md` в корне проекта
|
||||
- [ ] Сравнение по таблице (ФС vs Maildir vs текущий)
|
||||
- [ ] Рекомендация с обоснованием
|
||||
- [ ] Ссылка из README.md
|
||||
|
||||
---
|
||||
|
||||
## Задача 2 (приоритет №2): Локальный календарь (CalDAV) + Задачи
|
||||
|
||||
**Требование:** локальный сервис календаря/задач с нативной синхронизацией
|
||||
с Android-телефоном.
|
||||
|
||||
**Кандидаты (локальные, без облака):**
|
||||
- **Radicale** (CalDAV + CardDAV, лёгкий, Python, single-user, порт 5232) —
|
||||
лучший для календаря + задач (VTODO)
|
||||
- **Vikunja** (задачи + kanban, веб-UI, PostgreSQL, порт 8080) — если нужен
|
||||
полноценный трекер с веб-интерфейсом
|
||||
- **Leantime** (проекты/задачи, MySQL, порт 8080) — тяжелее
|
||||
|
||||
**Предложение:** Radicale для CalDAV-календаря + задач (нативный CalDAV на
|
||||
Android через DAVx5/DAVx5), а для **веб-трекера задач** (кнопка из почты)
|
||||
— **Vikunja** (API + веб-UI).
|
||||
|
||||
**Что сделать:**
|
||||
1. Поставить Radicale (docker или systemd) — CalDAV на 5232
|
||||
2. Создать календарь «Личный», «Рабочий» + коллекцию задач (VTODO)
|
||||
3. Поставить Vikunja (docker, postgres) — трекер на 8080
|
||||
4. Настроить пользователей/проекты
|
||||
|
||||
**Критерии готовности:**
|
||||
- [ ] Radicale отвечает на `curl -X PROPFIND http://127.0.0.1:5232/` → 207
|
||||
- [ ] Vikunja отвечает на `curl http://127.0.0.1:8080/api/v1/info` → 200
|
||||
- [ ] Cauerдок в README (порты, логины, пути)
|
||||
|
||||
---
|
||||
|
||||
## Задача 3 (приоритет №3): Нативная синхронизация с Android
|
||||
|
||||
**Стек Android:**
|
||||
- **DAVx5** (CalDAV/CardDAV-клиент) — синхронизация Radicale
|
||||
- Android встроенный Google-календарь через DAVx5 bridge
|
||||
- Задачи: приложение **Tasks.org** / **Vikunja android** / **CalDAV-совместимое**
|
||||
|
||||
**Что сделать:**
|
||||
1. Убедиться, что Radicale доступен снаружи (caddy reverse proxy, домен
|
||||
cal.nixg.ru) + TLS
|
||||
2. Настроить DAVx5 на телефоне (логин/пароль Radicale, URL cal.nixg.ru)
|
||||
3. Проверить двустороннюю синхронизацию (создать событие на телефоне → видно
|
||||
в Radicale → в веб-UI)
|
||||
|
||||
**Критерий готовности:**
|
||||
- [ ] Событие, созданное на Android, появляется в Radicale (и наоборот)
|
||||
- [ ] Задача создаётся на телефоне → видна в трекере
|
||||
|
||||
---
|
||||
|
||||
## Задача 1 (приоритет №4): Веб-интерфейс ассистента
|
||||
|
||||
**Список писем** с полями:
|
||||
- Дата
|
||||
- Адресант
|
||||
- Назначенные тэги
|
||||
- Папка (для перемещения)
|
||||
|
||||
**Кнопка создания задачи в трекере:**
|
||||
|
||||
**Стек:** FastAPI + SQLite FTS5 + Jinja2 (или React) — локально.
|
||||
|
||||
**Что сделать:**
|
||||
1. FastAPI-приложение: GET / (список писем), GET /email/<id> (детали),
|
||||
POST /email/<id>/move (перемещение в папку), POST /email/<id>/tag
|
||||
2. Интеграция с трекером: POST /tasks (создание задачи в Vikunja API)
|
||||
3. Страница авторизации (см. Задача 7)
|
||||
|
||||
**Критерии:**
|
||||
- [ ] `curl http://127.0.0.1:8085/` → список писем (дата, адресант, тэги, папка)
|
||||
- [ ] Перемещение письма в папку реально перемещает файл
|
||||
- [ ] Кнопка «Создать задачу» → POST в Vikunja API → задача создаётся
|
||||
|
||||
---
|
||||
|
||||
## Задача 6: Кнопка создания задачи в трекере (из веб-UI)
|
||||
|
||||
- POST /api/v1/tasks (Vikunja) с данными письма (тема, отправитель, ссылка)
|
||||
- В веб-UI кнопка «Создать задачу» на каждом письме
|
||||
|
||||
---
|
||||
|
||||
## Задача 7: Аутентификация веб-интерфейса
|
||||
|
||||
- Страница логина (логин/пароль)
|
||||
- Сессия/токен (FastAPI + JWT или Basic over TLS)
|
||||
- Reverse proxy: Caddy → TLS (домен mai.nixg.ru или email.nixg.ru)
|
||||
- Только локальный доступ (127.0.0.1) если не нужен внешний
|
||||
|
||||
---
|
||||
|
||||
## Порядок OpenSpec-цикл
|
||||
|
||||
1. change `email-storage-analysis` (Задача 4) — быстрый, влияет на архитектуру
|
||||
2. change `local-calendar-tasks` (Задача 2) — Radicale + Vikunja
|
||||
3. change `android-sync` (Задача 3) — DAVx5 + caddy + домен
|
||||
4. change `email-webui` (Задача 1) — FastAPI + UI
|
||||
5. change `email-webui-auth` (Задача 7)
|
||||
6. change `email-api` (Задача 6) — интеграция с Vikunja
|
||||
|
||||
Каждый — через `openspec new change <name>` → proposal/specs/design/tasks →
|
||||
validate → archive.
|
||||
@@ -1,7 +1,7 @@
|
||||
# Email Assistant — локальный архив и ассистент почты
|
||||
|
||||
**Дата:** 2026-07-19
|
||||
**Фаза:** 1.5 — Индексация, поиск, дайджесты + Адресная книга (в работе)
|
||||
**Дата:** 2026-09-11
|
||||
**Фаза:** 1.5–1.7 + Портфель веб-UI (планирование)
|
||||
|
||||
**Стек:** Himalaya CLI → Python → SQLite → Ollama (Qwen3:8b) → Yandex Disk
|
||||
|
||||
@@ -104,6 +104,37 @@ Hermes cron:
|
||||
|
||||
---
|
||||
|
||||
## Портфель: Веб-интерфейс + Календарь/Задачи (2026-09-11)
|
||||
|
||||
План: `PLAN_WEBUI.md`. Порядок: Задача 4 → 2 → 3 → 1 → 6 → 7.
|
||||
|
||||
### Задача 4: Анализ ФС vs Maildir ✅ (закрыта 2026-09-11)
|
||||
- [x] `STORAGE_ANALYSIS.md` — сравнение email.md/Maildir/MBOX/notmuch по 7 критериям
|
||||
- [x] Рекомендация: **остаться на email.md** + добавить `tags: []` в frontmatter + опц. экспорт в Maildir
|
||||
- [x] Ссылка в README; change `email-storage-analysis` заархивирован (openspec-lab)
|
||||
- [x] Факты: 4884 письма, 76 МБ, 81 контакт
|
||||
|
||||
### Задача 2: Локальный календарь + трекер задач 🔵 (в работе)
|
||||
- [x] Решение пользователя: **Radicale (CalDAV) + Vikunja (трекер)**, всё в Docker-контейнерах
|
||||
- [x] Change `local-calendar-tasks` создан и валиден (опenspec-lab) — proposal/specs/design/tasks
|
||||
- [x] **Radicale развёрнут**: контейнер, порт 5232, HTTP Basic (estorozhenko), PROPFIND → 207, без пароля → 401
|
||||
- [ ] Коллекции Radicale (Личный/Рабочий/Задачи) — создание через MKCOL вернуло 403 (Radicale 3.x создаёт коллекции иначе: PUT ресурса); **заблокировано ожиданием решения**
|
||||
- [ ] Vikunja — не начат (docker compose + postgres, порт 3456)
|
||||
- [ ] Caddy reverse proxy (cal.nixg.ru → 5232, tasks.nixg.ru → 3456)
|
||||
- [ ] Android-синхронизация (DAVx5)
|
||||
|
||||
### Задача 3: Нативная синхронизация с Android ⬜ (после Задачи 2)
|
||||
- [ ] DAVx5 на телефоне → Radicale; Vikunja app/token
|
||||
- [ ] Проверка двусторонней синхронизации (событие с телефона → bigbox)
|
||||
|
||||
### Задача 1: Веб-интерфейс ассистента ⬜
|
||||
- [ ] FastAPI + SQLite FTS5: список писем (дата/адресант/тэги/папка)
|
||||
- [ ] Перемещение в папку; тэги
|
||||
- [ ] Кнопка «Создать задачу» → в Vikunja API (Задача 6)
|
||||
- [ ] Страница авторизации (Задача 7)
|
||||
|
||||
---
|
||||
|
||||
## Решения и проблемы скриптов
|
||||
|
||||
### `mail_archive.py` — Инкрементальный архиватор
|
||||
@@ -181,18 +212,15 @@ Hermes cron:
|
||||
|
||||
## Текущие метрики
|
||||
|
||||
| Папка | Писем | Контакты извл. |
|
||||
|-------|-------|-----------------|
|
||||
| INBOX | 585 | — |
|
||||
| INBOX подпапки (18 хардкодных) | ~180 | — |
|
||||
| **Неархивируемые подпапки INBOX** | **~120 папок не синхронизируются** | **—** |
|
||||
| Sent | 515 | — |
|
||||
| Отправленные | 510 | — |
|
||||
| Archive | 373 | — |
|
||||
| **Всего** | **2073** | **5** |
|
||||
| Папка | Писем (2026-09-11) |
|
||||
|-------|---------------------|
|
||||
| INBOX (вкл. подпапки) | 2674 |
|
||||
| Archive | 876 |
|
||||
| Отправленные | 790 |
|
||||
| Sent | 544 |
|
||||
| **Всего** | **4884** (76 МБ, mail_index.db 5.4 MB) |
|
||||
|
||||
Индекс: 2073 письма, 5.4 MB SQLite.
|
||||
Контакты: 5 найдено (clean_body отрезает подпись в большинстве forwarded-писем).
|
||||
Контакты: **81** в contacts.json (не «5» — устарело; проверено 2026-09-11).
|
||||
|
||||
---
|
||||
|
||||
@@ -200,8 +228,8 @@ Hermes cron:
|
||||
|
||||
| ID | Имя | Расписание | Тип | Статус |
|
||||
|----|-----|-----------|-----|--------|
|
||||
| 22c5beb891cc | mail-archive-every-5min | every 5m | no-agent (скрипт) | ✅ |
|
||||
| 8e181a988392 | contacts-extractor-every-30m | every 30m | скрипт (--limit 15) | ✅ |
|
||||
| 5f2305b2bbf8 | mail-archive-every-5min | every 5m | no-agent (скрипт) | ✅ (Фаза 1.7: использует `--all --drain` с динамическим списком) |
|
||||
| ea0fd1ab4f93 | contacts-extractor-every-30m | every 30m | скрипт (--limit 15) | ✅ |
|
||||
| — | mail-index-incremental | not set | — | ❌ |
|
||||
| — | digest-weekly | not set | — | ❌ |
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# TODO — Email Assistant
|
||||
|
||||
Формат: | дата | задача | статус | закрыта в |
|
||||
|
||||
## 2026-07 (Фазы 1–1.5)
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
|---|---|---|---|
|
||||
| 2026-07-16 | Фаза 0.5: рефакторинг формата — meta.json+body.md → email.md (YAML-frontmatter) | ✅ закрыта | STATUS.md |
|
||||
| 2026-07-16 | Фаза 1: локальный архив (INBOX 585, Sent 515, Отправленные 510, Archive 373) | ✅ закрыта | STATUS.md |
|
||||
| 2026-07-16 | Фаза 1.5: mail_index.py (SQLite FTS5) + sqlite_search.py + digest.py | ✅ закрыта | STATUS.md |
|
||||
|
||||
## 2026-07-19
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
|---|---|---|---|
|
||||
| 2026-07-19 | Фаза 1.6: Contacts Extractor (LLM-подписи → contacts.json/vcf) | 🔵 в работе | |
|
||||
| 2026-07-19 | clean_body — вырезание цитируемой переписки (Outlook/forward) | ✅ закрыта | STATUS.md контекст |
|
||||
| 2026-07-19 | Cron mail-index-incremental | 🔵 открыта | |
|
||||
| 2026-07-19 | Cron digest-weekly | 🔵 открыта | |
|
||||
| 2026-07-19 | Push mirror gitea → gitverse.ru | 🔵 открыта | |
|
||||
|
||||
## 2026-09-11
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
|---|---|---|---|
|
||||
| 2026-09-11 | Фаза 1.7: динамическое обнаружение подпапок INBOX (get_inbox_subfolders, --all, fix run_cmd) | ✅ закрыта | STATUS.md §1.7, git c7430d1 |
|
||||
| 2026-09-11 | Анализ Nylas CLI → NYLAS_ANALYSIS.md + ссылка из README | ✅ закрыта | git d4bf3ed |
|
||||
| 2026-09-11 | Задача 4: Анализ ФС vs Maildir → STORAGE_ANALYSIS.md + README | ✅ закрыта | git e31b5f2; openspec change email-storage-analysis (архивирован) |
|
||||
| 2026-09-11 | Задача 2: Radicale развёрнут (docker :5232, HTTP Basic, PROPFIND 207) | 🔵 в работе | openspec change local-calendar-tasks |
|
||||
| 2026-09-11 | Задача 2: коллекции Radicale (Личный/Рабочий/Задачи) — MKCOL 403, ждёт решения способа | 🔵 заблокировано | |
|
||||
| 2026-09-11 | Задача 2: Vikunja (docker :3456, postgres) | 🔵 открыта | |
|
||||
| 2026-09-11 | Задача 2: Caddy reverse proxy (cal.nixg.ru, tasks.nixg.ru) | 🔵 открыта | |
|
||||
| 2026-09-11 | Задача 3: Android-синхронизация (DAVx5 → Radicale, Vikunja app) | 🔵 открыта | |
|
||||
| 2026-09-11 | Задача 1: веб-интерфейс (FastAPI, список писем, перемещение, тэги) | 🔵 открыта | |
|
||||
| 2026-09-11 | Задача 6: кнопка «Создать задачу» → Vikunja API | 🔵 открыта | |
|
||||
| 2026-09-11 | Задача 7: аутентификация веб-интерфейса | 🔵 открыта | |
|
||||
@@ -0,0 +1,94 @@
|
||||
# WALKTHROUGH — Email Assistant (капитанский журнал)
|
||||
|
||||
Воспроизводимость: хронология, команды, решения, ошибки и как чинили.
|
||||
|
||||
## 2026-09-11
|
||||
|
||||
### Фаза 1.7: динамическое обнаружение подпапок INBOX
|
||||
|
||||
**Проблема:** `mail_archive.py` хардкодил 18 подпапок INBOX, на сервере их 136+.
|
||||
|
||||
**Решение в `mail_archive.py`:**
|
||||
1. `get_inbox_subfolders()` — парсит `himalaya folder list` (таблица ASCII),
|
||||
фильтрует `INBOX/`, поддерживает глубину 3, fallback на хардкод при ошибке.
|
||||
2. `--all` = `FOLDERS + get_inbox_subfolders()` → 140 папок.
|
||||
3. `run_cmd()` — добавлен `try/except FileNotFoundError` (himalaya не в PATH).
|
||||
4. Таймаут envelope list поднят 60 → 180с: INBOX (14k писем) перечислялась >60с.
|
||||
|
||||
**Проверено:**
|
||||
- `get_inbox_subfolders()` → 136 папок, 120 глубоких.
|
||||
- `--folder "INBOX/!Реестр платежей/Акты" --limit 10 --drain` — работает.
|
||||
- `--folder "INBOX/!Завки/Либра" --limit 5 --drain` — 4 письма, OK.
|
||||
- Smoke `--all` с заглушкой archive_folder — 140 папок в списке.
|
||||
- Fallback (HIMALAYA_CMD=несуществующий) — работает.
|
||||
|
||||
**Зависание `--all`:** `envelope list` на большой INBOX висел >60с (таймаут).
|
||||
Поднятие до 180с + `--drain` (предохранитель 10k проходов) решило. Крон-обёртка
|
||||
`mail-archive.sh` переведена на `--all --drain` + `HOME=/home/estorozhenko`
|
||||
(himalaya не находил конфиг из-за смены HOME).
|
||||
|
||||
**Git:** c7430d1 «feat: динамическое обнаружение подпапок INBOX (Фаза 1.7)».
|
||||
|
||||
### Анализ Nylas CLI → отклонено
|
||||
|
||||
**Задача:** пользователь спросил, упростит ли Nylas получение/отправку писем.
|
||||
|
||||
**Вывод:** Nylas — **облачный SaaS** (api.us.nylas.com), не локальный CLI.
|
||||
Письма идут через серверы Nylas; платная подписка; IMAP-гранты гибнут при
|
||||
ротации пароля; Contacts API для generic IMAP платный. Для корпоративного
|
||||
IMAP + локального архива — **не подходит**.
|
||||
|
||||
**Artifacts:** `NYLAS_ANALYSIS.md` + ссылка в README. Git: d4bf3ed.
|
||||
|
||||
### Задача 4: Анализ ФС vs Maildir → закрыт
|
||||
|
||||
**Задача:** оценить, удобен ли формат `email.md` (ФС) vs Maildir/MBOX/notmuch,
|
||||
учитывая мотивацию — локальная LLM в скриптах без облака.
|
||||
|
||||
**Реальные данные (замер):** 4884 email.md, 76 МБ; INBOX 2674 (вкл. 1224 в 2026/),
|
||||
Archive 876, Отправленные 790, Sent 544; контактов 81 (а не 5!).
|
||||
|
||||
**Вывод (STORAGE_ANALYSIS.md):** **остаться на email.md** — он идеален для
|
||||
LLM-скриптов (`cat email.md | ollama run qwen3:8b`), человекочитаем, атомарен
|
||||
(папка UID на письмо). Maildir даёт стандартность, но raw-MIME (нужен парсер),
|
||||
нечитаемые имена, НЕТ тэгов. Эволюция: `tags: []` в frontmatter + опц. экспорт
|
||||
в Maildir + FTS5 остаётся.
|
||||
|
||||
**OpenSpec:** change `email-storage-analysis` (3 требования) → validate → archive
|
||||
(delta → openspec/specs/email-storage-format/spec.md). Git e31b5f2.
|
||||
|
||||
### Портфель: веб-UI + календарь/задачи (ПЛАН)
|
||||
|
||||
`PLAN_WEBUI.md` — 7 задач, порядок 4→2→3→1→6→7. Решения пользователя:
|
||||
- Трекер задач и календаря НЕТ → добавлять как отдельные сервисы.
|
||||
- **Все сервисы — в отдельных Docker-контейнерах.**
|
||||
- Стек: **Radicale (CalDAV) + Vikunja (трекер)**.
|
||||
|
||||
### Задача 2: Radicale (docker) — развёрнут частично
|
||||
|
||||
**Сделано:**
|
||||
1. `/opt/hermes/radicale/docker-compose.yml` (образ `kozea/radicale`, порт 5232).
|
||||
2. Конфиг `/opt/hermes/radicale/config/config` (htpasswd, owner_only, /data/collections).
|
||||
3. Пользователь `estorozhenko` — `htpasswd -c -b -m data/users estorozhenko <pass>`
|
||||
(пароль в `/opt/hermes/radicale/.env`, `RADICALE_PASS`).
|
||||
4. `docker compose up -d` → контейнер `radicale` работает.
|
||||
|
||||
**Проверено:** `curl -X PROPFIND http://127.0.0.1:5232/ -u estorozhenko:PASS` → **207**;
|
||||
без пароля → **401** ✅. Radicale 3.8.1.dev0, слушает 0.0.0.0:5232.
|
||||
|
||||
**Ошибка/урок:** `command: ["radicale", "-C", ...]` падал `unrecognized arguments:
|
||||
radicale` — entrypoint образа УЖЕ вызывает `radicale`, передавать только флаги:
|
||||
`command: ["-C", "/config/config"]`.
|
||||
|
||||
**Заблокировано:** создание коллекций. `MKCOL /Личный/` → **403**. Radicale 3.x
|
||||
создаёт коллекции иначе (PUT ресурса с Content-Type: text/calendar в новую
|
||||
коллекцию). Попытка проверки через PUT тестового VEVENT была **заблокирована
|
||||
таймаутом команды** (execution guard) — жду решение пользователя/возобновление.
|
||||
|
||||
**Осталось (Задача 2):** коллекции (Личный/Рабочий/Задачи), Vikunja (:3456,
|
||||
postgres), Caddy (cal.nixg.ru, tasks.nixg.ru), Android.
|
||||
|
||||
### Известные открытые хвосты (репозиторий)
|
||||
- `PLAN_WEBUI.md` — untracked (не закоммичен).
|
||||
- Cron mail-index-incremental и digest-weekly — не настроены.
|
||||
- Push mirror gitea → gitverse — не настроен (нужен токен [REDACTED]).
|
||||
Reference in New Issue
Block a user