Настроить/починить зеркало расшифровок встреч mymeet.ai и загрузку записи в облако на транскрибацию: "тянет чужие встречи", "api-ключ mymeet". Офлайн без облака - `local-transcription`.
Installs into .claude/skills of the current project.
Are you the author of Mymeet Snapshot?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/dewil-mymeet-snapshot)
---
name: mymeet-snapshot
description: Настроить/починить зеркало расшифровок встреч mymeet.ai и загрузку записи в облако на транскрибацию: "тянет чужие встречи", "api-ключ mymeet". Офлайн без облака - `local-transcription`.
---
# mymeet-snapshot
Скилл для первой настройки или починки локального snapshot расшифровок встреч из mymeet.ai через REST API. Скрипт тянет MD-отчеты проектных встреч и раскладывает по папкам `Встречи/` - для чтения, написания `.summary.md` и работы без захода в web-UI mymeet.
Скрипт-эталон лежит в каноне в `scripts/mymeet-snapshot.py` (top-level папка `scripts/`, не внутри папки скилла). `.AI` bootstrap типа management копирует его в `scripts/` в корне проекта - оттуда же, где его ожидает запуск (`python3 scripts/mymeet-snapshot.py`).
Архитектура повторяет `telegram-snapshot` / `redmine-snapshot`: общий секрет на устройстве, проектные указатели - в репозитории проекта.
## Когда применять
- Пользователь впервые настраивает `scripts/mymeet-snapshot.py` на новом устройстве.
- Подключается новый проект - нужно создать `.mymeet-snapshot.json` и проверить, что общая авторизация на устройстве уже есть.
- Скрипт тянет чужие (непроектные) встречи или кладет встречи не в ту папку - надо настроить правила `rules`.
- Первый запуск падает на авторизации (401) или на неизвестных полях ответа.
## Когда НЕ применять
- Обычный регулярный pull новых встреч - это просто Bash-команда (`python3 scripts/mymeet-snapshot.py --all`), отдельный скилл не нужен. Без флага скрипт только печатает справку и ничего не качает - не принимай это за выполненный pull.
- Анализ самих расшифровок - они уже лежат `.md` в `Встречи/`, читай напрямую.
- Чтение истории чатов - это `telegram-snapshot`. Задачи - `redmine-snapshot`.
## Что делает скрипт (модель)
1. `GET /api/workspaces/active/all-meetings` (пагинация `page`/`perPage`) - список встреч аккаунта.
2. Аккаунт mymeet общий, встреч много и не только этого проекта. Фильтр - **whitelist по правилам**: title встречи матчится подстроками из `rules[].match`; первое подошедшее правило задает папку (`rules[].dest`).
3. Подошла под правило + готовый статус (см. ниже) + есть дата -> качает MD (`GET /api/storage/download?format=md`) в `<meetings_root>/<dest>/ГГГГ-ММ-ДД.md` (коллизия в один день -> `-2`, `-3`).
4. НЕ подошла ни под одно правило -> в review-файл (`<meetings_root>/_mymeet-review.txt`) строкой `meeting_id | дата | title`. Человек глазами решает: проектную - добавить паттерн / дернуть вручную, чужую - игнорировать.
5. Что уже видел - помнит в `<meetings_root>/_mymeet-index.json`, повторно не качает.
JSON-отчет по умолчанию НЕ качается (для рутины хватает MD, на объемах JSON - оверхед). По требованию: `python3 scripts/mymeet-snapshot.py --json <meeting_id>` - положит `.json` рядом с MD этой встречи. Отдельного `-deltas` скрипта нет: новые встречи = то, что прогон только что скачал (печатается в конце), а не diff мутирующего файла, как у telegram/redmine.
Режимы (основной - точечный по id; аккаунт mymeet общий на проекты, поэтому балк по умолчанию выключен):
- `--pull <ID|URL>` - **основной режим**: точечно скачать одну встречу по правилам. Принимает и голый `meeting_id`, и URL встречи целиком (id берется из адреса - его видно в адресной строке mymeet). Рабочий цикл: пользователь правит имена спикеров на mymeet -> дает id/URL -> `--pull`.
- `--json <ID|URL>` - добрать JSON конкретной встречи (тоже принимает URL).
- `--list` - dry-run: что нашлось бы и куда легло, без скачивания.
- `--seed` - baseline: отметить все текущие встречи аккаунта как виденные, ничего не качая. Нужен на старте, если история уже скачана вручную: иначе балк создаст дубли существующих файлов (`-2`/`-3`).
- `--all` - балк: забрать ВСЕ новые проектные встречи разом (имена сырые, как распознал mymeet). По умолчанию НЕ запускается - голый прогон без аргументов печатает справку, чтобы на общем мульти-проектном аккаунте случайный запуск не нагреб чужое.
В `--pull` и `--all` статус берется из списка встреч сервиса. Только `processed` и `new` готовы. Любой другой или отсутствующий статус пропускается без скачивания и без ошибки. В stdout печатается `не готова: <id>, статус <статус>`; при отсутствии статуса выводится `нет статуса`.
Итог `--all` печатается в stdout:
- `OK:`, код 0 - все подходящие готовые встречи скачаны, включая прогон без новых встреч.
- `ЧАСТИЧНО:` со списком нескачанных id или ошибкой чтения списка, код 1 - есть ошибки скачивания или пагинации; успешные встречи сохраняются в индексе.
- Код 2 - ошибка конфига или авторизации (HTTP 401/403), если ничего не скачано. Если часть встреч скачана, ошибки авторизации дают `ЧАСТИЧНО:` и код 1.
Формат ответа API (подтверждено живым прогоном 2026-06-25): список - `GET all-meetings` -> `{followups: [...], total}`; у встречи поля `id`, `name` (title), `status` (`new`/`processed`/...), `date` в RFC 2822 (`"Thu, 25 Jun 2026 12:02:06 GMT"`). Скрипт читает их толерантно (перебор `*_KEYS`), дату парсит и ISO, и RFC 2822, и epoch.
## Алгоритм настройки
### Шаг 1. Проверить общую авторизацию устройства
```bash
ls ~/.config/mymeet-snapshot/
```
Должен быть `auth.json` (api_key, опционально base_url) с правами 600. Если файла нет - шаг 2.
### Шаг 2. Получить API-ключ mymeet
1. Залогиниться в mymeet.ai.
2. Settings -> API key (`https://app.mymeet.ai/ru/settings/api-key`).
3. Скопировать ключ. Доступно на тарифах Lite, Pro, Business.
### Шаг 3. Заполнить auth.json
```bash
mkdir -p ~/.config/mymeet-snapshot
chmod 700 ~/.config/mymeet-snapshot
```
Содержимое `~/.config/mymeet-snapshot/auth.json`:
```json
{
"api_key": "<ключ из шага 2>",
"base_url": "https://backend.mymeet.ai",
"use_curl": true
}
```
- `base_url` можно опустить - дефолт `https://backend.mymeet.ai`.
- `use_curl` - поднять в `true`, если Python падает на SSL (`CERTIFICATE_VERIFY_FAILED`). Сборка python.org на macOS не имеет CA-бандла и не проверяет даже валидный публичный сертификат `backend.mymeet.ai`; curl берет сертификат из системного хранилища. На практике на macOS нужен `true`.
```bash
chmod 600 ~/.config/mymeet-snapshot/auth.json
```
### Шаг 4. Подключить проект (.mymeet-snapshot.json)
В корне проекта - `.mymeet-snapshot.json`:
```json
{
"meetings_root": "Встречи",
"rules": [
{ "match": ["синк", "заказчик"], "dest": "с заказчиком/{YYYY}/{MM}" },
{ "match": ["дейли", "дейлик", "команд"], "dest": "с командой/{YYYY}/{MM}" },
{ "match": ["1-1", "one-on-one"], "dest": "1-1/_inbox" }
],
"review_file": "Встречи/_mymeet-review.txt"
}
```
- `meetings_root` - корневая папка встреч относительно корня проекта.
**В проекте с несколькими направлениями** (клиент-зонтик, `rules/client-umbrella.md`) единой папки встреч нет: встречи направления живут внутри него (`rules/artifacts-structure.md`). Тогда `meetings_root` - корень проекта (`"."`), а полный путь несет `dest` каждого правила:
```json
{ "match": ["ShopHack"], "dest": "2026-07 Agent/Встречи/с командой/{YYYY}/{MM}" },
{ "match": ["Харитонов"], "dest": "Встречи/1-1/Харитонов Евгений" }
```
Индекс `_mymeet-index.json` всегда лежит относительно `meetings_root`, поэтому при смене корня **перенеси существующий индекс руками** в новое место: на пустом индексе следующий прогон посчитает все старые встречи новыми и скачает их повторно. `review_file` - отдельный ключ (по умолчанию тоже относительно `meetings_root`); при `"."` задай его явно, иначе review осядет в корне.
- `rules` - список `{match: [подстроки title], dest: путь-шаблон}`. Первое правило, чья подстрока нашлась в title (регистронезависимо), задает папку. `dest` поддерживает `{YYYY}`/`{MM}`/`{DD}` из даты встречи.
- `review_file` - куда писать непроектные/нераспознанные встречи.
Файл - **не секрет** (ключа нет), коммитится.
> **Важно про title.** mymeet галлюцинирует в шапке (путает имена/стороны), поэтому матчинг по title не стопроцентный. Это осознанный компромисс: вместо тихого пропуска все непонятное падает в review-файл, где видно глазами. Не вылизывай правила до идеала - расширяй по мере того, как реальные встречи попадают в review. 1-1 по title в папку конкретного сотрудника не развести - кладем в `1-1/_inbox`, дальше руками.
### Шаг 5. Dry-run, потом первый pull
Сначала **всегда** `--list` - покажет, что API реально отдает и куда ляжет, ничего не качая:
```bash
cd <project-root>
python3 scripts/mymeet-snapshot.py --list
```
Сверь: проектные встречи распознались и нацелены в верные папки, чужое ушло бы в review. Если поля даты/title/id читаются неверно (видно по "дата?" или пустым title) - см. "Типичные ошибки". Когда раскладка верная:
```bash
python3 scripts/mymeet-snapshot.py --all
```
Флаг обязателен: голый запуск печатает справку и ничего не качает. История уже скачана вручную - вместо `--all` сначала `--seed`, иначе балк создаст дубли. Дальше рабочий цикл - точечный `--pull <ID|URL>`. Зависимостей нет - чистый stdlib (`urllib`).
### Шаг 6. Типичные ошибки
**`ssl.SSLCertVerificationError: CERTIFICATE_VERIFY_FAILED`.** Python не видит CA-бандл (типично для python.org-сборки на macOS). В `~/.config/mymeet-snapshot/auth.json` выставить `"use_curl": true` - запросы пойдут через curl, который берет сертификат из системного хранилища. Перезапустить.
**`401 Unauthorized`.** Неверный/просроченный api_key - перевыпустить (шаг 2). Проверить тариф (нужен Lite/Pro/Business).
**В `--list` у всех "дата?".** В ответе списка поле даты названо не так, как перебирает скрипт (`DATE_KEYS`). Посмотреть сырой ответ: `curl -H "X-API-KEY: <ключ>" "https://backend.mymeet.ai/api/workspaces/active/all-meetings?page=0&perPage=5"`, найти ключ с датой, добавить его первым в `DATE_KEYS` в скрипте (и оформить как upstream-правку в канон).
**Пустые title / не тот id.** Аналогично - проверить реальные имена полей в сыром ответе и поправить `TITLE_KEYS` / `ID_KEYS`.
**Все ушло в review, хотя встречи проектные.** Правила `rules[].match` не совпали с реальными title - расширить подстроки под то, как mymeet называет встречи (видно в review-файле).
**Перенес уже забранные встречи в другую папку.** Поправь у них в `_mymeet-index.json` поля `dest` и `file`: индекс хранит, куда файл лег, и точечный `--pull` этой встречи идет по записанному пути. Со старым путем будет одно из двух - если там пусто, приедет второй экземпляр рядом с перенесенным; если место уже занято другим документом, скрипт его перезапишет (прежнее содержимое уедет в `<имя>.prev.md`, в stderr будет предупреждение с путем и размерами).
**В файле шапка mymeet (супер краткое содержание, саммари, задачи).** Скрипт режет ее сам по маркеру `**Транскрипт:**` - если шапка на месте, значит маркера не нашлось (сменился формат) и в stderr было предупреждение. Файл сохранен целиком намеренно: резать по догадке нельзя, потеряется транскрипт. Убрать шапку вручную, а расхождение формата - оформить upstream-правкой в канон. Почему шапку не храним - `rules/meeting-transcripts.md`.
**MD пришел пустой / это JSON.** `download?format=md` мог вернуть не файл, а JSON со ссылкой на storage - скрипт это обрабатывает (следует по `url`/`download_url`/`link`). Если формат ответа иной - глянуть сырой ответ download и поправить `download_md`.
## Загрузка записи на транскрибацию
Обратная половина цикла: не только забрать готовую расшифровку, но и положить запись в сервис. Нужна, когда записи нет в облачном календаре сервиса - телефонный звонок, локальный файл, запись с диктофона.
```bash
python3 scripts/mymeet-snapshot.py --upload звонок.mp3 --title "2026-08-04 Клиент - созвон"
python3 scripts/mymeet-snapshot.py --upload запись.m4a --template one-to-one --speakers 2 --wait
python3 scripts/mymeet-snapshot.py --status <meeting_id>
```
- `--title` - имя встречи. **Задавать сразу и по проектной конвенции**: под правило матчинга попадает имя, и запись с именем "voice_2026.m4a" осядет в review-списке вместо своей папки.
- `--template` - шаблон отчета сервиса (`default-meeting`, `one-to-one`, `hr-interview`, `lecture-notes`, `protocol` и др.; полный список в скрипте).
- `--speakers N` - число участников, если известно.
- `--wait` - дождаться обработки (поллинг статуса) и сразу забрать расшифровку по правилам конфига. Без флага скрипт печатает `meeting_id` и команду проверки статуса; транскрибация занимает минуты.
- Файл читается и шлется чанками, поэтому длинная запись не съедает память.
**Агент не запускает `--upload` без явного "ок" пользователя на конкретный файл.** Отдельного dry-run-гейта у команды нет намеренно (в отличие от `telegram-send`, где агент сочиняет текст сам): файл называет пользователь, и удваивать рутину подтверждением флага незачем. Но действие необратимо и платно, поэтому подтверждение остается - на уровне поведения, а не флага.
**Загрузка расходует минуты аккаунта** и создает встречу в общем workspace. Тестовые прогоны оставляют мусорные встречи, которые видны всем проектам, использующим этот аккаунт, - удалять через веб-интерфейс (API удаления скрипт не трогает).
### Грабли протокола
- **`localTime` на финализирующем чанке.** В OpenAPI 1.1.0 поле не помечено обязательным. 2026-07-28 без него финализация отдавала **голый 500 от nginx** (`text/html`, не JSON; `error_format=json` на такой ответ не действует) - в проекте-источнике это восемь дней принимали за поломку сервиса и писали в поддержку. На проверке 2026-08-04 сервер принял финализацию и без поля: вендор починил молча. Скрипт шлет поле всегда и сам - полагаться на негарантированное поведение нельзя.
- **Отсюда общий прием:** при интеграции сверяйся с **эталонной реализацией вендора**, а не только со спекой. Расхождение "в спеке поле необязательное, в примере кода оно есть в каждом запросе" - сигнал; проявляется оно не валидационной ошибкой, а 500, который читается как поломка чужой стороны.
- **Поле `file` обязательно даже у пустого финализирующего маркера** - без самого поля приходит 400.
- Коды ответа говорят по делу: `402` кончились минуты, `422` загрузка битая или вытеснена более новой попыткой (бывает при параллельных заливках одного файла), `426` не хватает минут на файл такой длины, `432` больше 1 ГБ нужен тариф выше, `433` запись короче 30 секунд, `414` больше 3 ГБ, `415` формат.
## Жесткие правила
1. **auth.json - НЕ в проектную папку.** Проект на Yandex.Disk; утечка api_key в облако недопустима. Только `~/.config/mymeet-snapshot/` (локально на устройство).
2. **api_key - секрет.** В `.mymeet-snapshot.json`, который коммитится, ключа быть не должно.
3. **Whitelist, не pull-all.** Аккаунт общий; качать все подряд нельзя. Непроектное - в review, не на диск.
4. **JSON - по требованию.** Рутинно держим только MD. JSON (`--json`) - точечно, когда нужна программная обработка (`word_timings`/`chapters`).
5. **Выгруженный JSON - технический артефакт: место ему вне синкаемой папки, а не под ignore после факта** (`rules/docs-maintenance.md`, раздел "Технические артефакты в синкаемой папке"): исключение защищает следующий файл, уже созданный оно не возвращает. Заметки встреч - наоборот, содержимое: они остаются в проекте. Исходных записей это не касается - у них жизненный цикл строже: после проверенной транскрибации запись из проекта убирается совсем (`rules/meetings.md`, "Записи: в проекте не храним"), а не остается лежать под ignore.
## Связанные файлы
- `scripts/mymeet-snapshot.py` в корне проекта - pull MD проектных встреч по whitelist, `--list` (dry-run), `--json <id>` (добор JSON).
- `~/.config/mymeet-snapshot/auth.json` - локальные credentials на устройство (api_key, base_url). Не в проекте.
- `.mymeet-snapshot.json` в корне проекта - проектные указатели `{meetings_root, rules, review_file}`. Не секрет.
- `<meetings_root>/_mymeet-index.json` - реестр уже скачанных встреч (что не качать повторно).
- `<meetings_root>/_mymeet-review.txt` - очередь непроектных/нераспознанных встреч на ручное решение.