@retensy/mcp 0.21.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  "name": "retensy-mcp",
8
8
  "source": "./",
9
9
  "version": "0.15.1",
10
- "description": "Сборка и публикация воронок ботов (Telegram/MAX, Retensy Bots) из описания, рассылки, подключение ботов и сервисов, сайтов из блоков + публикация статей блога в Markdown — через MCP + скилл."
10
+ "description": "Сборка и публикация воронок ботов (Telegram/MAX, Retensy Bots) из описания или переноса из другого конструктора (BotHelp, SaleBot, Senler и др.), рассылки, подключение ботов и сервисов, сайтов из блоков + публикация статей блога в Markdown — через MCP + скилл."
11
11
  }
12
12
  ]
13
13
  }
@@ -2,7 +2,7 @@
2
2
  "name": "retensy-mcp",
3
3
  "displayName": "Retensy MCP",
4
4
  "version": "0.15.1",
5
- "description": "MCP-сервер + скилл для Retensy Bots: подключение ботов и сервисов, сборка и публикация воронок ботов (Telegram/MAX), рассылки, сайты из блоков (Zero-блок, дизайны, шаблоны, свои домены, заявки) и статьи блога в Markdown — всё одним токеном zmcp_.",
5
+ "description": "MCP-сервер + скилл для Retensy Bots: подключение ботов и сервисов, сборка и публикация воронок ботов (Telegram/MAX), перенос воронок из других конструкторов (BotHelp, SaleBot, Senler, Leadteh, Smartsender, ManyChat и др.), рассылки, сайты из блоков (Zero-блок, дизайны, шаблоны, свои домены, заявки) и статьи блога в Markdown — всё одним токеном zmcp_.",
6
6
  "author": { "name": "retensy", "url": "https://bots.retensy.com" },
7
7
  "homepage": "https://bots.retensy.com/bots",
8
8
  "repository": "https://github.com/retensy/retensy-mcp",
package/README.md CHANGED
@@ -7,15 +7,61 @@
7
7
 
8
8
  MCP-сервер (+ скилл для Claude Code) для **сборки и публикации воронок/автоматизаций ботов (Telegram, MAX и Instagram)** в сервисе [retensy `/bots`](https://bots.retensy.com/bots): из текстового описания → валидный граф сценария → заливка и публикация через API.
9
9
 
10
+ ---
11
+
12
+ ## Быстрый старт
13
+
14
+ 1. **Получи токен.** Залогинься на https://bots.retensy.com → открой `/bots/mcp-tokens` → создай токен → скопируй секрет `zmcp_...` (показывается один раз).
15
+ 2. **Подключи у своего клиента** (env-переменные везде одни и те же: `RETENSY_BASE_URL`, `RETENSY_MCP_TOKEN`):
16
+
17
+ **Claude Desktop** — добавь в `claude_desktop_config.json`:
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "retensy-mcp": {
22
+ "command": "npx",
23
+ "args": ["-y", "@retensy/mcp@latest"],
24
+ "env": {
25
+ "RETENSY_BASE_URL": "https://bots.retensy.com",
26
+ "RETENSY_MCP_TOKEN": "zmcp_ваш_токен"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ **Claude Code** — одной командой:
34
+ ```bash
35
+ claude mcp add retensy-mcp -e RETENSY_BASE_URL=https://bots.retensy.com -e RETENSY_MCP_TOKEN=zmcp_ваш_токен -- npx -y @retensy/mcp@latest
36
+ ```
37
+ Или как плагин (подтянет и скиллы, см. [«Вариант A» ниже](#вариант-a--как-плагин-claude-code-рекомендуется)):
38
+ ```text
39
+ /plugin marketplace add retensy/retensy-mcp
40
+ /plugin install retensy-mcp@retensy
41
+ ```
42
+
43
+ **Cursor** — Settings → MCP → Add new MCP Server (или `.cursor/mcp.json` в проекте) — тот же JSON-объект, что и для Claude Desktop выше (`mcpServers.retensy-mcp` с `command`/`args`/`env`).
44
+
45
+ 3. **Собери первую воронку.** Опиши сценарий словами агенту — он сам соберёт граф и опубликует (см. [«Использование»](#использование)), либо перенеси готовую воронку из другого сервиса — см. [«Перенос воронки из другого конструктора»](#перенос-воронки-из-другого-конструктора) ниже.
46
+
47
+ Проверить подключение в любом клиенте: спроси агента «настрой подключение» — вызовет `setup` и подскажет, если что-то не так.
48
+
49
+ ---
50
+
10
51
  - 🤖 **Сценарии ботов**: `list_bots`, `list_graphs`, `list_channels`, `get_graph`, `create_graph`, `update_graph`, `edit_graph_live`, `patch_graph`, `dry_run`, `publish_graph`, `import_funnel`, `list_templates`, `create_graph_from_template`, `clone_graph`, `copy_graph`, `rename_graph`, `set_active_graph`, `delete_graph`, `upload_file`, `list_files`, `delete_file`, `graph_analytics`, `list_bot_users`, `list_links` (+ `setup`/`set_token`).
11
52
  - 🔌 **Боты и сервисы**: `create_bot` (Telegram/MAX по токену), `bot_stop`/`bot_resume`, `connect_integration`/`disconnect_integration` (amoCRM, Битрикс24, GetCourse, Я.Метрика, ЮKassa; Google Таблицы — ссылкой на вход Google; креды хранятся зашифрованными и не возвращаются), `integration_catalog`/`integration_status`/`integration_test` (каталог Integration Core и живая проверка подключения), `integration_ingress_url` (URL ингресса «Внешних событий» — Tilda, JivoSite, WooCommerce, amoCRM, Битрикс24, GetCourse, CloudPayments, Robokassa, Т-Банк, Prodamus — для `TRIGGER_WEBHOOK`), `channel_post` (пост в канал Telegram/MAX).
12
- - 📣 **Рассылки**: `broadcast_preview` (размер аудитории), `broadcast_send` (сейчас или по расписанию, сразу по нескольким ботам; сообщения или запуск сценария), `broadcast_list`/`broadcast_get`/`broadcast_cancel`, повторяющиеся (`broadcast_recurring`), черновики (`broadcast_drafts`, `broadcast_duplicate`) + скилл `send-broadcast`.
53
+ - 📣 **Рассылки**: `broadcast_preview` (размер аудитории), `broadcast_send` (сейчас или по расписанию, сразу по нескольким ботам; сообщения или запуск сценария; полный фильтр аудитории `audienceFilter`, дедуп `dedup` и распределённый запуск `stagger` по ботам), `broadcast_list`/`broadcast_get`/`broadcast_cancel`, повторяющиеся (`broadcast_recurring`), черновики (`broadcast_drafts`, `broadcast_duplicate`), мульти-бот группы (`broadcast_group` — preview/list/get/pause/resume/cancel) + скилл `send-broadcast`.
54
+ - 👥 **Аудитория подписчиков**: `audience_search`/`audience_fields` — кросс-бот поиск и фильтр (теги, даты, статус, условия по переменным), `audience_tag_edit`/`audience_variable_edit` — массовое редактирование тегов/переменных, `audience_export` — потоковая выгрузка выборки в CSV/XLSX, `dialog_search` — поиск по «Диалогам» (включая текст сообщений).
55
+ - 📤 **Миграция подписчиков** из других конструкторов: `migration_presets`/`migration_preview`/`migration_commit` — generic CSV column-mapper для BotHelp/SaleBot/Senler/Leadteh/Smartsender/ManyChat (переносятся только подписчики — ни один из них не публикует документированный формат экспорта воронки).
56
+ - 📅 **Запись на слоты**: `booking_calendar_*` (буферы до/после брони, горизонт записи, типы встреч своей длительности, форма клиента, публичная страница `/book/<slug>`), `booking_confirm`/`booking_reject` — ручное подтверждение брони (`confirmationMode:"MANUAL"`).
57
+ - 📰 **Публикации и VK**: `publications_usage` — месячная квота (общая для Telegram/MAX/Instagram/VK), `vk_status`/`vk_connect`/`vk_disconnect`/`vk_post` — подключение VK-сообщества токеном и публикация на стену.
13
58
  - 🔗 **Ссылка вместо отказа**: что нельзя сделать через API (вход через Google/Instagram, оплата тарифа, вход в аккаунт) — инструмент возвращает прямую ссылку и одну строку, что сделать.
14
59
  - 📝 **Статьи блога** (тот же токен `zmcp_…`): `article_publish`, `article_update`, `article_list`, `article_get` — публикация статей в Markdown (как README на GitHub) в раздел **/articles**.
15
60
  - 🧠 **ИИ-агенты и база знаний**: `agent_list`/`agent_get`/`agent_create`/`agent_update`/`agent_publish`/`agent_health`/`agent_test_chat`/`agent_unanswered`, `kb_docs`/`kb_add_qa`/`kb_add_text`/`kb_add_site`/`kb_reindex` — настройка ИИ-агента и его базы знаний (раздел «ИИ-агенты»).
16
61
  - 📎 **Медиа**: `upload_file` грузит фото/видео/документы в библиотеку **/bots/files** (до 50 МБ) и возвращает публичный URL — его вставляешь в медиа-карточку сценария.
17
62
  - 🌐 **Сайты из блоков** (раздел «Страницы»): создание, правка операциями (блоки, Zero-блок со свободной вёрсткой, код блока, папки страниц, дизайны, шаблоны из библиотеки), публикация и откат, свои домены, заявки из форм и куда их доставлять — инструменты `site_*` + скилл `build-site`.
18
63
  - 🧠 **Скилл `build-bot-funnel`**: учит агента собирать корректный граф (типы узлов, ветки, кнопки, задержки) и проверять его перед публикацией. Поддерживает Telegram, MAX и Instagram.
64
+ - 🔁 **Скилл `rebuild-funnel-from-builder`**: переносит воронку из другого конструктора (BotHelp, SaleBot, Senler, Leadteh, Smartsender, ManyChat, PuzzleBot и др.) по export JSON/тексту, скриншотам или пересказу — в граф Retensy, новым черновиком, без перезаписи текущего сценария. См. [«Перенос воронки из другого конструктора»](#перенос-воронки-из-другого-конструктора).
19
65
  - 📦 **Без зависимостей** — чистый Node ≥18, ставится и запускается сразу.
20
66
 
21
67
  ### Поддерживаемые платформы
@@ -37,7 +83,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
37
83
  /plugin install retensy-mcp@retensy
38
84
  ```
39
85
 
40
- Подтянутся MCP-сервер и скиллы `build-bot-funnel`, `build-site`, `send-broadcast`. Проверить: `/mcp` и `/plugin`.
86
+ Подтянутся MCP-сервер и скиллы `build-bot-funnel`, `rebuild-funnel-from-builder`, `build-site`, `send-broadcast`. Проверить: `/mcp` и `/plugin`.
41
87
 
42
88
  ### Вариант B — как обычный MCP-сервер (Claude Code / Cursor / Windsurf / любой MCP-клиент)
43
89
 
@@ -75,6 +121,10 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
75
121
 
76
122
  Отозвать токен можно там же — доступ блокируется мгновенно.
77
123
 
124
+ > С 14.10 тот же токен `zmcp_...` полностью покрывает и вебхук-сценарии (`/api/scenarios` — `scenario_*`),
125
+ > и управление подключениями сервисов (`/api/integrations` — `connect_integration`/`disconnect_integration`/
126
+ > `integration_update` и остальные `integration_*`), не только `/api/bots`. Отдельного токена под эти инструменты не нужно.
127
+
78
128
  > **Не знаешь, что делать?** Скажи агенту «настрой подключение» — он вызовет `setup`, объяснит шаги и попросит токен. Любой инструмент при отсутствии токена тоже вернёт пошаговую инструкцию.
79
129
 
80
130
  > Дев-окружение: `RETENSY_BASE_URL=http://localhost:8066`.
@@ -134,7 +184,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
134
184
  | `site_create(title, slug?, template?)` | новый сайт из блоков → `id`; `template` (`starter`/`blank`/`mini-landing`) — сразу черновик из шаблона; число сайтов ограничено тарифом — сверх лимита HTTP 402 с причиной и ссылкой на смену тарифа |
135
185
  | `site_get(siteId, saveToFile?)` | модель сайта (`revision`, `draft`, `versions[]` публикаций) |
136
186
  | `site_schema()` | JSON Schema модели и операций — читать перед правкой |
137
- | `site_edit(siteId, ops[]?, revision?, init?)` | правка операциями (`init`: `starter`/`blank`/`mini-landing`; только `init` — черновик из шаблона), всё или ничего: страницы и папки, блоки, Zero-элементы, код блока (`get/set/add_block_code`), дизайны и их кадры, шаблоны (`add_template`), тема, попапы |
187
+ | `site_edit(siteId, ops[]?, revision?, init?)` | правка операциями (`init`: `starter`/`blank`/`mini-landing`; только `init` — черновик из шаблона), всё или ничего: страницы и папки, блоки, Zero-элементы, код блока (`get/set/add_block_code`), дизайны и их кадры, шаблоны (`add_template`), тема, попапы; пиксели аналитики (GA4/GTM/Meta/VK/Яндекс.Метрика) — тоже сюда, `set_settings {analytics: {...}}` (отдельного инструмента нет — те же `settings.analytics` модели сайта, валидируются форматом id на сервере) |
138
188
  | `site_templates(category?, full?)` | библиотека шаблонов блоков для `add_template` |
139
189
  | `site_publish(siteId)` | опубликовать черновик → `url` |
140
190
  | `site_rollback(siteId, revision)` | вернуть прошлую публикацию |
@@ -145,11 +195,12 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
145
195
  | `broadcast_list(botId?, group?, page?, size?)` | рассылки + счётчики разделов (черновики/запланированные/отправленные/повторы) |
146
196
  | `broadcast_get(broadcastId)` | рассылка целиком: статус, счётчики, сообщения, причины ошибок |
147
197
  | `broadcast_preview(botIds, tagsAll?, tagsNone?)` | сколько подписчиков получат рассылку |
148
- | `broadcast_send(name, botIds, messages \| graphId, tagsAll?, tagsNone?, scheduledAt?, draftId?)` | отправить сейчас / запланировать; по нескольким ботам; или запуск сценария |
149
- | `broadcast_cancel(broadcastId)` | отменить запланированную/идущую |
150
- | `broadcast_recurring(action, …)` | повторяющиеся рассылки: list / create (DAILY·MONTHLY·YEARLY) / stop |
198
+ | `broadcast_send(name, botIds, messages \| graphId, tagsAll?/tagsNone? или audienceFilter?, scheduledAt?, draftId?, dedup?, stagger?)` | отправить сейчас / запланировать; по нескольким ботам; или запуск сценария; `dedup`/`stagger` → мульти-бот группа (`broadcast_group`) вместо прямых рассылок |
199
+ | `broadcast_cancel(broadcastId)` | отменить запланированную/идущую (для группы — `broadcast_group action:"cancel"`) |
200
+ | `broadcast_recurring(action, …)` | повторяющиеся рассылки: list / create (DAILY·MONTHLY·YEARLY, те же `audienceFilter`/`dedup`/`stagger`) / stop |
151
201
  | `broadcast_drafts(action, …)` | черновики: list / get / create / update / delete |
152
202
  | `broadcast_duplicate(broadcastId` или `draftId)` | копия рассылки или черновика — новый черновик «… (копия)» |
203
+ | `broadcast_group(action, …)` | мульти-бот группа с дедупом/распределённым запуском: `preview` (read-only оценка) / `list` / `get` / `pause` / `resume` / `cancel` (каскадом на уже запущенные боты) |
153
204
  | `article_list()` | свои статьи блога (id, slug, title, просмотры) |
154
205
  | `article_get(slug)` | статья по slug (Markdown content, excerpt, обложка) |
155
206
  | `article_publish(content, title?, cover?, excerpt?)` | новая статья (Markdown; title из `# ...`, если не задан; обложка из `cover`-URL или 1-й картинки → OG; `excerpt` явно или авто) → id, slug, URL |
@@ -194,7 +245,41 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
194
245
  | `booking_calendar_create(name, zone?, slotMinutes?, hours?, exceptions?, botId?)` | новый календарь: окна `{day:1–7, from:"HH:mm", to:"HH:mm"}`, исключения `{date, from?, to?}` |
195
246
  | `booking_calendar_update(calendarId, …поля)` / `booking_calendar_delete(calendarId, confirm:true)` | правка (поля накладываются на текущие) / удаление |
196
247
  | `booking_slots(calendarId, from?, to?, limit?)` | свободные слоты `[{at, label}]`, даты ГГГГ-ММ-ДД в зоне календаря |
197
- | `booking_list(calendarId, from?)` / `booking_create(calendarId, slotAt, name?, phone?)` / `booking_cancel(calendarId, bookingId)` | брони: список / ручная запись (занятый слот — ошибка «занят») / отмена |
248
+ | `booking_list(calendarId, from?, pendingOnly?)` / `booking_create(calendarId, slotAt, name?, phone?, email?, comment?, customAnswers?, meetingTypeId?)` / `booking_cancel(calendarId, bookingId)` | брони: список (или только ждущие подтверждения) / ручная запись (занятый слот — ошибка «занят») / отмена |
249
+ | `booking_confirm(calendarId, bookingId)` / `booking_reject(calendarId, bookingId)` | подтвердить/отклонить бронь при `confirmationMode:"MANUAL"` (прочие поля календаря: `breaks`, `meetingTypes`, буферы, `horizonDays`, `intakeFields`, `notifyOwner`, `slug`+`enabled` — публичная ссылка `/book/<slug>` — в `booking_calendar_create`/`_update`) |
250
+ | `audience_fields(botId, search?, limit?)` | ключи переменных (с выведенным типом) и теги бота — для построителя фильтра |
251
+ | `audience_search(botId?, botIds?, filter?, q?, sort?, dir?, page?, size?)` | фильтр+сортировка подписчиков: один бот или кросс-бот (без `botId`); `filter` — полный `AudienceFilter` (теги any/all/none, диапазоны дат, `blocked`, `variableConditions`) |
252
+ | `audience_tag_edit(botId, chatIds, add?, remove?)` / `audience_variable_edit(botId, chatIds, set?, unset?)` | массово добавить/снять теги / установить/снять переменные у выбранных подписчиков |
253
+ | `audience_export(botId?, botIds?, filter?, q?, format, savePath?)` | потоковая выгрузка ТЕКУЩЕЙ отфильтрованной выборки, `csv`\|`xlsx` (xlsx требует `savePath` — бинарный формат), потолок 100000 строк |
254
+ | `dialog_search(q, limit?)` | кросс-бот поиск по «Диалогам» — имя/username/id/текст сообщения |
255
+ | `publications_usage(botId)` | месячная квота публикаций владельца (общая для Telegram/MAX/Instagram/VK) |
256
+ | `vk_status()` / `vk_connect(communityId, accessToken)` / `vk_disconnect(accountId)` | подключённые VK-сообщества / подключить токеном сообщества (проверяется живым вызовом VK) / отключить |
257
+ | `vk_post(accountId, text?, mediaUrl?)` | пост на стену VK (только текст/фото — без видео), списывает ту же квоту публикаций |
258
+ | `migration_presets()` | каталог источников миграции подписчиков (BotHelp/SaleBot/Senler/Leadteh/Smartsender/ManyChat/универсальный CSV) |
259
+ | `migration_preview(botId, path, platform, mapping?)` | пробный прогон CSV (ничего не пишет): без `mapping` — определение заголовков/подсказка сопоставления, с `mapping` — полный dry-run (новые/существующие, превышение лимита) |
260
+ | `migration_commit(botId, path, platform, mapping)` | реальный импорт подписчиков тем же `mapping`, что подтвердил `migration_preview` |
261
+ | `chain_list()` / `chain_get(chainId)` | цепочки публикаций («Публикации» → «Цепочки», автопостинг source → destinations[]) / одна цепочка целиком |
262
+ | `chain_create(chain)` / `chain_update(chainId, chain)` | создать / изменить цепочку целиком (`chain`: `name`, `source` TELEGRAM\|VK\|RSS, `destinations[]` TELEGRAM\|MAX\|VK\|INSTAGRAM, `steps[]` FILTER\|TEXT_TRANSFORM\|AI_ADAPT\|UTM\|DELAY, `approvalMode?`, `utmDefaults?`); HTTP 409 — прямой цикл с другой цепочкой |
263
+ | `chain_delete(chainId, confirm:true)` | удалить цепочку (теряется история заданий) |
264
+ | `chain_enable(chainId)` / `chain_pause(chainId)` | включить / поставить на паузу приём новых постов источника |
265
+ | `chain_runs(chainId, page?, size?)` | история заданий цепочки, новые сверху (бэкенд без постраничной выдачи — `page`/`size`, если переданы, режут список в MCP) |
266
+ | `chain_approve(jobId)` / `chain_reject(jobId)` | согласовать / отклонить задание, ждущее подтверждения (`approvalMode` AI_ONLY/ALL) |
267
+ | `chain_retry(jobId)` | повторить проваленное задание вручную, минуя бэкофф |
268
+ | `site_analytics(siteId, from?, to?)` | KPI+тренд сайта: просмотры, клики (только с `goalName`), заявки, топ-страницы/источники (по умолчанию последние 30 дней) |
269
+ | `site_analytics_events(siteId, from?, to?, page?, size?)` | сырые события сайта постранично (`PAGE_VIEW`\|`CLICK`\|`FORM_SUBMIT`, UTM) |
270
+ | `bot_subscriber_analytics(botId, from, to)` | новые подписчики/активность бота по дням (дашборд аналитики, диапазон до 92 дней) |
271
+ | `scenario_timeseries(graphId, from, to)` | дневной ряд прогонов сценария `{ok, failed, other}` (по узлам — `graph_analytics`) |
272
+ | `agent_tool_test(agentId, toolId, params?)` | проверить инструмент агента (`AMO_LOOKUP`/`SHEETS_LOOKUP`/`HTTP_API`) напрямую, без модели — бюджет ИИ не тратится |
273
+ | `site_lead_file_download(siteId, leadId, fileId)` | метаданные вложения заявки + ссылка на скачивание (сам файл через MCP не передаётся — HEAD-запрос, не GET) |
274
+ | `scenario_list()` | сценарии автора, включая вебхук-сценарии (`sourceType: WEBHOOK`\|`BOT`) — в `list_graphs` вебхук-сценарии не входят |
275
+ | `scenario_create(name, botId?)` | создать сценарий: без `botId` — вебхук-сценарий (сервер сам выпускает путь входа и секрет), с `botId` — бот-сценарий, сразу привязанный к боту |
276
+ | `scenario_get(scenarioId)` | сценарий целиком — граф + `source` (у вебхук-сценария: путь/секрет входа) + `outputBinding` |
277
+ | `scenario_update_source(scenarioId, botId?, channel?, recipientFrom?)` | привязка выхода вебхук-сценария, заменяется ЦЕЛИКОМ (без `botId` — привязка снимается, без `recipientFrom` — очищается); только для `sourceType:"WEBHOOK"` |
278
+ | `scenario_publish(scenarioId)` | опубликовать вебхук-сценарий (бот-сценарий — через `publish_graph`) |
279
+ | `scenario_rotate_secret(scenarioId)` | выпустить новый секрет входа вебхук-сценария — старый сразу перестаёт приниматься |
280
+ | `agent_api_key_create(agentId, name?)` / `agent_api_key_list(agentId)` / `agent_api_key_revoke(agentId, keyId, confirm:true)` | API-ключи публичного чата агента (`zagt_...`, секрет виден один раз) — для `POST /api/public/agents/{agentId}/chat` без основного токена |
281
+ | `ai_packs()` | каталог пакетов ответов ИИ сверх тарифа + текущий баланс (`aiPackAnswersLeft`) |
282
+ | `ai_pack_purchase(sku, method?, confirm:true)` | купить пакет — возвращает ссылку на оплату, деньги не списываются этим вызовом |
198
283
 
199
284
  ### Рассылки
200
285
 
@@ -255,6 +340,20 @@ node skills/build-bot-funnel/validate.mjs path/to/import.json --platform=MAX
255
340
 
256
341
  ---
257
342
 
343
+ ## Перенос воронки из другого конструктора
344
+
345
+ Есть готовая воронка в BotHelp, SaleBot, Senler, Leadteh, Smartsender, ManyChat, PuzzleBot или
346
+ любом другом сервисе — скилл [`rebuild-funnel-from-builder`](skills/rebuild-funnel-from-builder/SKILL.md)
347
+ учит агента перенести её в Retensy: разобрать export JSON/текст/скриншоты/пересказ → построить
348
+ инвентарь блоков → сопоставить с реальными типами узлов Retensy (таблица в
349
+ [reference/mapping.md](skills/rebuild-funnel-from-builder/reference/mapping.md)) → прямо спросить
350
+ про то, что не мапится, вместо того чтобы выдумывать замену → собрать граф → проверить
351
+ `validate.mjs`/`dry_run` → залить **новым** черновиком (никогда не поверх текущего сценария) →
352
+ опубликовать только после явного подтверждения. Перенос самих подписчиков (не графа) — отдельно,
353
+ через `migration_presets`/`migration_preview`/`migration_commit` (CSV).
354
+
355
+ ---
356
+
258
357
  ## Разработка
259
358
 
260
359
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retensy/mcp",
3
- "version": "0.21.2",
3
+ "version": "0.24.0",
4
4
  "description": "MCP server to build and publish Telegram and MAX bot funnels/automations, broadcasts, service integrations, block-based websites (incl. free-layout Zero blocks, designs, templates, custom domains, form leads) and Markdown blog articles in the Retensy Bots service. Zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "scripts": {
22
22
  "start": "node src/index.mjs",
23
23
  "check": "node --check src/index.mjs",
24
- "test": "node scripts/smoke.mjs && node scripts/telemetry.test.mjs && node scripts/live-edit-422.test.mjs && node scripts/broadcast-connect.test.mjs && node scripts/integration-core.test.mjs && node scripts/site-init.test.mjs && node scripts/ai-agents.test.mjs && node scripts/battery-tools.test.mjs && node scripts/parity-tools.test.mjs && node scripts/parity-2.test.mjs && node scripts/validate-graph.test.mjs && node scripts/call-scenario.test.mjs",
24
+ "test": "node scripts/smoke.mjs && node scripts/telemetry.test.mjs && node scripts/live-edit-422.test.mjs && node scripts/broadcast-connect.test.mjs && node scripts/integration-core.test.mjs && node scripts/site-init.test.mjs && node scripts/ai-agents.test.mjs && node scripts/battery-tools.test.mjs && node scripts/parity-tools.test.mjs && node scripts/parity-2.test.mjs && node scripts/validate-graph.test.mjs && node scripts/call-scenario.test.mjs && node scripts/audience-publications.test.mjs && node scripts/chains-analytics.test.mjs && node scripts/platform-decisions.test.mjs",
25
25
  "validate": "node skills/build-bot-funnel/validate.mjs"
26
26
  },
27
27
  "engines": {
@@ -41,6 +41,7 @@ description: Собрать воронку (сценарий) бота для с
41
41
  - **`TRIGGER_PAYMENT` («оплата прошла»)** — если счёт выставлял бот, подписчик определяется САМ (счёт помнит его в metadata платежа): можно сразу слать сообщение и вешать метку, разбирать ответ кассы не нужно. Если оплата пришла мимо бота — прогон headless, получателя задавай через `config.target`. Ставь фильтр `minAmount`/`descriptionContains`, иначе сценарий сработает на любую оплату в кассе.
42
42
  - **`TRIGGER_SCHEDULE` («По расписанию»)** — `{cron:"0 9 * * 1-5", timezone}`: headless-прогон без чата (отправка — только с `config.target`), для сводок, эскалаций, SLA. Журнал — `scenario_runs`.
43
43
  - **Запись на слоты и напоминание**: календарь создаётся `booking_calendar_create`; в `ACTIONS` — `booking_slots` (список в `{{var.slots}}`) → вопрос → `booking_book` (ветка `taken` — слот занят) → `DELAY UNTIL {{var.slot_at}}` с `offset:"-24h"` → напоминание; `booking_cancel` освобождает слот. **Подписки**: `invite_link_create`/`invite_link_revoke`, `subscription_extend`/`subscription_check`, `yookassa_charge_saved`. **Передача оператору** — `agent_chat` (бот замолкает до «Вернуть боту»). Поля — в schema.md.
44
+ - **Переиспользуемые подсценарии — `call_scenario`** (действие в `ACTIONS`, ставь ПОСЛЕДНИМ в блоке): `graphId` — сценарий ЭТОГО ЖЕ бота (`list_graphs`), `mode: "goto"` (по умолчанию, уйти без возврата) или `"call"` (вызвать и вернуться — после `END`/конца цели движок продолжает вызывающего с места после блока). Общая глубина `goto`+`call` в одной цепочке — не больше 5 переходов/вызовов подряд. Коды ошибок и рантайм-поведение — в validation.md.
44
45
  - **Статус прогона `PARTIAL`** («завершён с ошибками») — сценарий дошёл до конца, но действие упало (шаг `ok:false`, `error` «КОД: …»). Проверяй `bot_user_runs`/`scenario_runs`, а ответы CRM/HTTP — `integration_calls`.
45
46
  - **`TRIGGER_SITE_FORM` («Заявка с сайта»)** — `{siteId, formId}`: посетитель отправил форму на ОПУБЛИКОВАННОМ сайте владельца (`site_list` → id сайта, id формы — из `site_get`). Пустой/отсутствующий `formId` = любая форма сайта. Поля формы — `{{var.<имя поля>}}`; имя, почта, телефон всегда доступны как `{{var.name}}`, `{{var.email}}`, `{{var.phone}}` (если поле названо иначе — находятся по подписи), UTM — `{{var.utm_source}}` и т.п. Чата с посетителем нет: узлы отправки работают только с явным `config.target`, CRM/таблицы/вебхуки — как обычно. Обычно это сценарий без бота (источник WEBHOOK, как вебхук-сценарий) — ему `TRIGGER_WEBHOOK` не нужен; можно и в сценарии бота. Чужой сайт → `SITE_FORM_FOREIGN_SITE`, без сайта → `SITE_FORM_NO_SITE`. Одна заявка — один запуск; `TRIGGER_ANY_UPDATE` на заявки не срабатывает.
46
47
 
@@ -73,8 +73,15 @@
73
73
  Бот должен быть админом канала/группы, иначе события оттуда не придут.
74
74
  - `TRIGGER_ANY_UPDATE` — `{}`, ловит ЛЮБОЙ апдейт. Приоритет самый низкий: срабатывает, только
75
75
  если не подошёл ни один конкретный триггер. Удобен как «ничего не понял» / отладка.
76
- - `TRIGGER_WEBHOOK` — `{}`, точка входа сценария с источником WEBHOOK (не бот). Такие сценарии
77
- создаются в вебе; у графа бота этот триггер не сработает.
76
+ - `TRIGGER_WEBHOOK` — `{}`, точка входа сценария с источником WEBHOOK (не бот); у графа БОТ-сценария
77
+ этот триггер не сработает. Такой сценарий создаётся `scenario_create(name)` без `botId` — сервер сам
78
+ выпускает путь входа и секрет (видны в `scenario_get` → `source.config`, ротируются `scenario_rotate_secret`);
79
+ узлы/рёбра — как у обычного графа, через `update_graph`/`edit_graph_live`/`patch_graph` по вернувшемуся id.
80
+ Выходной бот (кому слать ответ) задаётся/снимается отдельно через `scenario_update_source(scenarioId, botId?,
81
+ channel?, recipientFrom?)` — поле заменяется ЦЕЛИКОМ, без `botId` сценарий остаётся headless. Публикуется
82
+ такой сценарий `scenario_publish`, НЕ `publish_graph` (у него на source.type WEBHOOK — 409). Список своих
83
+ сценариев, включая вебхук-сценарии (их нет в `list_graphs`), — `scenario_list`; удаление — `delete_graph`
84
+ (тем же id). Собрать так же можно и в вебе — оба пути ведут в один и тот же объект.
78
85
  - **Внешние события интеграции** — тот же `TRIGGER_WEBHOOK`, но с заполненными
79
86
  `{ "provider": "<provider-ключ из integration_catalog, нижний регистр>", "connectionId": "<id из list_integrations>",
80
87
  "event": "<events[].key нужного события из inbound.events того же provider в integration_catalog>" }`.
@@ -179,7 +186,11 @@ IG-боты не поддерживают команды (`/start`). Вход
179
186
  - **передача оператору**: `agent_chat` (без полей) — «позвать человека»: диалог встаёт на паузу (бот и ИИ молчат, входящие копятся в «Диалогах»), владельцу уходит уведомление; без бота уведомлений действие помечается `NOT_CONFIGURED`, пауза всё равно включается. Вернуть боту — кнопка в «Диалогах» или MCP `dialog_handoff {active:false}`. В dry-run и headless-прогонах пропускается.
180
187
  - **срок ответа оператора (SLA)**: `sla_check` — опц. `maxLevel` (0 — без потолка), `timezone` (для `sla_at`, по умолч. Europe/Moscow). Ставь первым в блоке `ACTIONS` после `DELAY` на срок ответа, ниже в том же блоке — эскалация (`add_tag`, `notify`). Оператор не писал в чат после `agent_chat` → `sla_breached`=`1`, `sla_level` +1, `sla_at` — ISO со смещением, действия блока выполняются; ответил / диалог у бота / тот же таймер повторно → `sla_breached`=`0`, остальные действия блока пропускаются. Новая передача — уровни с 1. Ветвиться дальше — `CONDITION` по `{{var.sla_level}}`.
181
188
  - **запись на слоты** (календарь — `booking_calendar_create`/`booking_calendar_list`): `booking_slots` — `calendarId`, опц. `days` (1–62, по умолч. 7), `limit` (1–50, по умолч. 10), `saveTo` (по умолч. `slots`) → переменные `slots` (нумерованный список для сообщения), `slots_json`, `slots_count`; `booking_book` — `calendarId`, `slotAt` (по умолч. `{{var.slot_at}}`; можно номер из списка «2», ISO-время или подпись слота), опц. `slotsVar`, `name`/`phone` (по умолч. `{{var.name}}`/`{{var.phone}}`) → `booking_id`, `slot_at` (ISO с зоной — для напоминания `DELAY UNTIL {{var.slot_at}} offset -24h`), `slot_label`, `booking_status`; занятый слот → выход блока **`taken`** (если ребро нарисовано, иначе `error`); `booking_cancel` — `bookingId` (по умолч. `{{var.booking_id}}`), освобождает слот.
182
- - **переход в другой сценарий**: `call_scenario` — `graphId` (сценарий ЭТОГО ЖЕ бота того же владельца, id из `list_graphs`; черновик тоже можно), опц. `nodeId` (стартовый блок цели; по умолч. триггер `/start` → первый триггер → первый блок без входящих рёбер — так стартует подсценарий без триггера), `variables: [{ "key":"plan", "value":"{{var.x}}" }]` (ключи `[a-z][a-z0-9_]{0,63}`, служебные `_*` запрещены — `ACTION_BAD_KEY`, в рантайме пропускаются, значения — шаблоны; сохраняются подписчику ДО перехода), `mode` — только `"goto"` (по умолч.). Подписчик уходит в целевой сценарий отдельным прогоном, текущий прогон завершается `OK`; ответы на вопросы и кнопки цели доходят до неё, хотя активный сценарий бота другой (`session.currentGraphId`). Ставь ПОСЛЕДНИМ в блоке (`CALL_SCENARIO_NOT_LAST`), не на себя (`CALL_SCENARIO_SELF`), только `goto` (`CALL_SCENARIO_BAD_MODE`), без цели — `CALL_SCENARIO_NO_TARGET`, чужой/несуществующий/вебхук-сценарий — `CALL_SCENARIO_FORBIDDEN` при публикации. В рантайме отказ = шаг `ok=false`, ошибка `CODE: call_scenario — …`, блок в `error` (нет ребра — `next`): `CALL_SCENARIO_RECURSION` (сценарий уже в цепочке переходов одного события), `CALL_SCENARIO_DEPTH` (> 5 переходов подряд), `CALL_SCENARIO_NOT_FOUND`, `CALL_SCENARIO_FORBIDDEN`, `CALL_SCENARIO_NO_ENTRY`, `CALL_SCENARIO_NO_SUBSCRIBER` (headless-вебхук), `NOT_CONFIGURED` (бэкенд в режиме развязанных сценариев — переход недоступен). Сбой более раннего действия блока не прячется переходом: шаг `ok=false` с кодом того сбоя, прогон `PARTIAL`, переход при этом выполняется. Цель-черновик исполняется как есть — правки черновика сразу видят подписчики, уже перешедшие в него. «Вызвать и вернуться» нет — для возврата поставь в конце цели свой `call_scenario` обратно (сработает, если между ними был ответ подписчика/задержка, иначе `RECURSION`).
189
+ - **переход в другой сценарий**: `call_scenario` — `graphId` (сценарий ЭТОГО ЖЕ бота того же владельца, id из `list_graphs`; черновик тоже можно), опц. `nodeId` (стартовый блок цели; по умолч. триггер `/start` → первый триггер → первый блок без входящих рёбер — так стартует подсценарий без триггера), `variables: [{ "key":"plan", "value":"{{var.x}}" }]` (ключи `[a-z][a-z0-9_]{0,63}`, служебные `_*` запрещены — `ACTION_BAD_KEY`, в рантайме пропускаются, значения — шаблоны; сохраняются подписчику ДО перехода), `mode` — `"goto"` (по умолч.) или `"call"`. Ставь ПОСЛЕДНИМ в блоке для обоих режимов (`CALL_SCENARIO_NOT_LAST`), не на себя (`CALL_SCENARIO_SELF`), только `goto`/`call` (иначе `CALL_SCENARIO_BAD_MODE`), без цели — `CALL_SCENARIO_NO_TARGET`, чужой/несуществующий/вебхук-сценарий — `CALL_SCENARIO_FORBIDDEN` при публикации.
190
+ - `mode:"goto"` (по умолчанию) — подписчик уходит в целевой сценарий отдельным прогоном, текущий прогон завершается `OK`; ответы на вопросы и кнопки цели доходят до неё, хотя активный сценарий бота другой (`session.currentGraphId`). Возврата нет — для «вернуться» нужно либо `mode:"call"`, либо свой `call_scenario` обратно в конце цели (сработает, только если между ними был ответ подписчика/задержка, иначе `CALL_SCENARIO_RECURSION`).
191
+ - `mode:"call"` — «вызвать и вернуться»: перед переходом движок кладёт возвратный кадр (вызывающий graphId + узел, на который вести после — следующий по ребру `next` этого же блока, либо ничего, если ребра нет) в `session.callStack` (персистентно, переживает рестарт сервера). Когда цель доходит до `END` или до конца графа (рёбер больше нет), движок снимает кадр и продолжает вызывающего С ЭТОГО МЕСТА новым прогоном — удобно для переиспользуемых подсценариев («спросить телефон», «проверить оплату») без копипасты.
192
+ - Общий счётчик глубины/цикла — ОБЩИЙ для `goto` и `call` в одной цепочке: не больше 5 переходов/вызовов подряд (`CALL_SCENARIO_DEPTH`), повтор графа, уже бывшего в цепочке — `CALL_SCENARIO_RECURSION` (для `call` цепочка — это персистентный `callStack`, не только текущий тик).
193
+ - В рантайме отказ = шаг `ok=false`, ошибка `CODE: call_scenario — …`, блок в `error` (нет ребра — `next`): `CALL_SCENARIO_RECURSION`, `CALL_SCENARIO_DEPTH`, `CALL_SCENARIO_NOT_FOUND`, `CALL_SCENARIO_FORBIDDEN`, `CALL_SCENARIO_NO_ENTRY`, `CALL_SCENARIO_NO_SUBSCRIBER` (headless-вебхук), `NOT_CONFIGURED` (бэкенд в режиме развязанных сценариев — переход недоступен). Сбой более раннего действия блока не прячется переходом: шаг `ok=false` с кодом того сбоя, прогон `PARTIAL`, переход/вызов при этом выполняется. Цель-черновик исполняется как есть — правки черновика сразу видят подписчики, уже перешедшие в него.
183
194
  - **контакт заявки**: `lead_link_contact` — `phone`/`email` (по умолч. `{{var.phone}}`/`{{var.email}}`) → склейка с единым контактом владельца по телефону E.164 или email, `{{var.contact_id}}`.
184
195
  - **платный доступ / подписки** (бот должен быть админом группы/канала): `invite_link_create` — персональная ссылка на одно вступление: опц. `expireHours` (по умолч. 24), `name`, `saveTo` (по умолч. `invite_link`), `send` (false — не отправлять), `text`; `invite_link_revoke` — `link` (по умолч. `{{var.invite_link}}`); `subscription_extend` — `period` (`1mo`, `30d`, `1y`, `2w`, `12h`), опц. `var` (по умолч. `sub_until`), `timezone` — продлевает от max(сейчас, текущий срок); `subscription_check` — опц. `var`, `saveTo` (по умолч. `sub_active`) → `true`/`false`; `yookassa_charge_saved` — рекуррентное списание по сохранённому способу оплаты: `connectionId` (ЮKassa), `amount`, `description`, опц. `paymentMethodId` (по умолч. `{{var.yk_payment_method_id}}`), `periodKey` (по умолч. `{{var.sub_until}}` — защита от двойного списания за период). Доступ продлевает не списание, а сценарий `TRIGGER_PAYMENT` → `subscription_extend`.
185
196
  - **Meta Conversions API** (Integration Core): `meta_capi_event` — `connectionId`, `eventName` (Purchase/Lead/…), опц. `dealId`, `stage`, `eventId`, `email`/`phone` (сырые — хешируются SHA-256), `value`, `currency`, `actionSource`. Поля других провайдеров — в `integration_catalog` (`actions[].inputs`).
@@ -47,6 +47,14 @@
47
47
  - `SCHEDULE`: `isoDate` = `YYYY-MM-DD`, ISO-8601 дата-время или шаблон `{{var.x}}` (`SCHEDULE_BAD_DATE`); `time` = `HH:mm` — обязателен при голой дате (`SCHEDULE_BAD_TIME`); опц. `offset` (`SCHEDULE_BAD_OFFSET`), `timezone` (`SCHEDULE_BAD_TIMEZONE`).
48
48
  - `TRIGGER_SCHEDULE`: `cron` ровно из 5 полей, не чаще раза в минуту (`SCHEDULE_TRIGGER_BAD_CRON`); `timezone` — известная зона (`SCHEDULE_TRIGGER_BAD_TIMEZONE`).
49
49
  - `ACTIONS`: непустой `actions[]` (`ACTIONS_EMPTY`); каждый `kind` — из допустимого списка (`ACTION_UNKNOWN_KIND`, список — в schema.md); per-kind: `tag` (`ACTION_BAD_TAG`), `set_field.key` (`ACTION_BAD_KEY`), `url` у `external_request`/`subscriber_webhook` (`ACTION_BAD_URL`), `calendarId` у `booking_slots`/`booking_book` (`ACTION_BOOKING_NO_CALENDAR`) и их `saveTo` (`ACTION_BAD_KEY`). Действия Integration Core (`actions[].kind` из `integration_catalog`, например `meta_capi_event`) бэкенд узнаёт по реестру провайдеров; `validate.mjs` их оффлайн не знает и выдаёт предупреждение, а не ошибку.
50
+ - `ACTIONS` с действием `call_scenario` (переход/вызов другого сценария того же бота того же владельца), зеркало `GraphValidator.validateScenarioCalls`:
51
+ - должно быть **последним** действием в блоке, для обоих `mode` (`CALL_SCENARIO_NOT_LAST`);
52
+ - `graphId` обязателен — сценарий этого же бота, `list_graphs` (`CALL_SCENARIO_NO_TARGET`); не сам текущий граф (`CALL_SCENARIO_SELF`, сравнение без учёта регистра);
53
+ - `mode` — `"goto"` (по умолчанию) или `"call"`, другое значение → `CALL_SCENARIO_BAD_MODE`;
54
+ - `variables[].key` ∈ `[a-z][a-z0-9_]{0,63}`, служебные `_*` запрещены → `ACTION_BAD_KEY`;
55
+ - чужой/несуществующий/вебхук-сценарий-цель → `CALL_SCENARIO_FORBIDDEN` (проверяется при публикации, не офлайн-валидатором).
56
+ - В рантайме (оба `mode` делят один и тот же счётчик глубины/цикла): не больше **5** переходов/вызовов подряд в одной цепочке (`CALL_SCENARIO_DEPTH`); граф, уже бывший в этой цепочке — `CALL_SCENARIO_RECURSION` (для `mode:"call"` цепочка — персистентный `session.callStack`, не только текущий тик); прочие рантайм-коды: `CALL_SCENARIO_NOT_FOUND`, `CALL_SCENARIO_NO_ENTRY`, `CALL_SCENARIO_NO_SUBSCRIBER` (headless-вебхук), `NOT_CONFIGURED` (бэкенд в режиме развязанных сценариев).
57
+ - `mode:"goto"` — текущий прогон завершается `OK`, подписчик продолжает в цели отдельным прогоном, возврата нет. `mode:"call"` — перед переходом в `session.callStack` кладётся кадр (вызывающий граф + узел после блока по ребру `next`, если есть); когда цель доходит до `END`/конца графа, кадр снимается и вызывающий продолжается с этого места новым прогоном.
50
58
  - `ACTIONS` с действием `issue_invoice` (счёт на кассу владельца):
51
59
  - `connectionId` обязателен (`YK_NO_CONNECTION`) — подключение с `provider=YOOKASSA` из `list_integrations`;
52
60
  - `amount` обязателен (`YK_NO_AMOUNT`), число > 0 или шаблон `{{var.x}}` (`YK_BAD_AMOUNT`);
@@ -403,8 +403,8 @@ for (const n of nodes) {
403
403
  // Зеркало GraphValidator.validateScenarioCalls. Владение целью (сценарий этого же бота) проверит publish_graph.
404
404
  if (i !== c.actions.length - 1)
405
405
  errors.push(`CALL_SCENARIO_NOT_LAST: ${who} — переход в сценарий должен быть последним действием в блоке.`);
406
- if (a.mode != null && String(a.mode) !== "goto")
407
- errors.push(`CALL_SCENARIO_BAD_MODE: ${who} — поддерживается только mode «goto» (перейти).`);
406
+ if (a.mode != null && !["goto", "call"].includes(String(a.mode)))
407
+ errors.push(`CALL_SCENARIO_BAD_MODE: ${who} — mode: «goto» (перейти) или «call» (вызвать и вернуться).`);
408
408
  (Array.isArray(a.variables) ? a.variables : []).forEach((v, j) => {
409
409
  const key = String(v?.key ?? "");
410
410
  if (!VAR_RE.test(key) || key.startsWith("_"))
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: rebuild-funnel-from-builder
3
+ description: Перенести воронку (сценарий бота) из другого конструктора — BotHelp, SaleBot, Senler, Leadteh, Smartsender, ManyChat, PuzzleBot или любого другого — в Retensy. На входе export JSON/текст, скриншоты редактора или устное описание воронки другого сервиса; на выходе валидный граф retensy-bot-graph, созданный НОВЫМ черновиком (никогда не перезаписывая существующий активный сценарий), проверенный validate.mjs/dry_run и опубликованный только после подтверждения пользователя. Использовать, когда пользователь просит «перенеси воронку из BotHelp/SaleBot/...», «импортируй бота с другого конструктора», присылает экспорт JSON/CSV другого сервиса, скриншоты его редактора сценариев или пересказывает словами, как была устроена воронка там.
4
+ ---
5
+
6
+ # Перенос воронки из другого конструктора в Retensy
7
+
8
+ Превращает воронку, собранную в ДРУГОМ сервисе (BotHelp, SaleBot, Senler, Leadteh, Smartsender,
9
+ ManyChat, PuzzleBot — или любом не названном здесь), в валидный граф `retensy-bot-graph`. Источник —
10
+ что угодно: export JSON/CSV, скопированный текст из экспорта, скриншоты экрана редактора, либо
11
+ устный пересказ пользователем «как это было устроено».
12
+
13
+ Это не альтернатива [`build-bot-funnel`](../build-bot-funnel/SKILL.md), а его надстройка для
14
+ конкретного случая «source = чужой конструктор»: формат графа, типы узлов, правила валидатора,
15
+ публикация — всё то же самое, описано там. Прочитай
16
+ [`../build-bot-funnel/reference/schema.md`](../build-bot-funnel/reference/schema.md) и
17
+ [`../build-bot-funnel/reference/validation.md`](../build-bot-funnel/reference/validation.md) —
18
+ это единственный источник правды о том, какие типы узлов РЕАЛЬНО существуют в Retensy. Не выдумывай
19
+ несуществующий тип узла только потому, что в другом конструкторе была похожая кнопка.
20
+
21
+ ## Когда применять
22
+
23
+ Пользователь присылает экспорт/скриншоты/описание воронки ДРУГОГО бот-конструктора и просит её
24
+ «перенести», «воссоздать», «импортировать», «сделать то же самое у нас». Если он просто описывает
25
+ воронку своими словами без привязки к другому сервису — это обычный `build-bot-funnel`.
26
+
27
+ ## Рабочий процесс
28
+
29
+ ### 1. Собери вход и опиши его как инвентарь блоков
30
+
31
+ Прими то, что есть, без требования единого формата:
32
+ - **Export JSON/XML/CSV** другого конструктора — прочитай файл целиком, не фрагментом.
33
+ - **Вставленный текст** экспорта (многие сервисы отдают воронку как список шагов текстом/таблицей).
34
+ - **Скриншоты редактора** — опиши по ним каждый блок: тип, текст, кнопки и их подписи, куда ведёт
35
+ каждая кнопка (по стрелке или по названию следующего блока).
36
+ - **Устный пересказ** — задавай уточняющие вопросы по узлам (см. п.3), не додумывай недостающее
37
+ самостоятельно без спроса.
38
+
39
+ Построй **плоский инвентарь блоков** ДО всякого маппинга: порядковый номер/id в исходнике, тип блока
40
+ как он назван у источника (напр. «Сообщение», «Условие», «Задержка», «Webhook», «Оплата»), текст/содержимое,
41
+ исходящие связи (на какой блок/по какому условию). Это промежуточный список, который потом построчно
42
+ переводится в узлы Retensy — не пропускай этот шаг и не иди сразу в генерацию JSON по памяти о скриншотах.
43
+
44
+ ### 2. Сопоставь концепции с типами узлов Retensy
45
+
46
+ Таблица сопоставления — в [reference/mapping.md](reference/mapping.md): по каждому общему понятию
47
+ builders (сообщение, кнопка, задержка, тег, переменная, условие/ветка, оплата, форма, интеграция,
48
+ AI/бот-ответчик, рассылка, вебхук) — какой РЕАЛЬНЫЙ тип узла/конфиг Retensy его покрывает, и ссылка
49
+ на точный раздел schema.md. Особенности конкретных источников (BotHelp, SaleBot, Senler, Leadteh,
50
+ Smartsender, ManyChat, PuzzleBot) и частые ловушки каждого — там же.
51
+
52
+ Общее правило маппинга: сопоставляй **по смыслу шага**, а не по названию блока источника. «Условие»
53
+ в одном конструкторе может оказаться у нас `CONDITION` (да/нет), `BRANCH` (A/B) или `SWITCH`
54
+ (детерминированный выбор по значению) — смотри, что блок реально делает.
55
+
56
+ ### 3. Зафиксируй немаппируемое и спроси пользователя
57
+
58
+ После прохода по инвентарю у тебя будет три корзины:
59
+ - **Маппится напрямую** — идёт в граф.
60
+ - **Маппится с потерей/приближением** (напр. специфичный виджет оплаты источника → наш `issue_invoice`
61
+ без части его настроек) — помечай и предупреждай пользователя, что именно теряется.
62
+ - **Не маппится вовсе** (нет такого типа узла/действия в Retensy — см. schema.md) — **не выдумывай
63
+ обходной путь молча**. Перечисли пользователю такие блоки списком («в источнике был блок X — в
64
+ Retensy аналога нет; варианты: пропустить / заменить на Y / реализовать иначе») и жди решения,
65
+ прежде чем собирать граф целиком. Это тот же принцип, что в `build-bot-funnel`: отказ инструмента
66
+ на несуществующую возможность уже регистрируется автоматически на сервере — тебе отдельно
67
+ дублировать это не нужно, просто не подменяй отсутствующее самоделкой.
68
+
69
+ Не выдумывай маркетинговые тексты за пользователя — если текст сообщения не виден в источнике
70
+ (обрезан на скриншоте, не указан в экспорте), спроси его или пометь `[ВСТАВЬ ТЕКСТ]` только если
71
+ пользователь сам разрешил так делать.
72
+
73
+ ### 4. Построй граф
74
+
75
+ Дальше — как в `build-bot-funnel`: дерево узлов и рёбер, у каждой кнопки-выбора свой `btn_N`,
76
+ валидные UUID, хотя бы один корневой `TRIGGER_*`, без циклов вне `ASK_QUESTION`/`DELAY`/`SCHEDULE`,
77
+ без узлов `END`. Все детали типов/конфигов — в schema.md, не здесь (чтобы не держать два
78
+ расходящихся описания одного формата).
79
+
80
+ Особенность переноса: исходная воронка почти всегда имела **одну** точку входа (команда/кнопка
81
+ запуска у источника). Замени её на подходящий Retensy-триггер по платформе назначения
82
+ (`TRIGGER_COMMAND` для Telegram/MAX, `TRIGGER_IG_DM`/`TRIGGER_IG_COMMENT`/... для Instagram) — см.
83
+ раздел триггеров в schema.md и платформенные отличия в самом начале `build-bot-funnel/SKILL.md`.
84
+
85
+ ### 5. Проверь локально
86
+
87
+ ```bash
88
+ node "<путь к build-bot-funnel>/validate.mjs" <путь к import.json>
89
+ # Для IG-бота:
90
+ node "<путь к build-bot-funnel>/validate.mjs" <путь к import.json> --platform=INSTAGRAM
91
+ # Для MAX-бота:
92
+ node "<путь к build-bot-funnel>/validate.mjs" <путь к import.json> --platform=MAX
93
+ ```
94
+
95
+ Исправь всё, что покажет валидатор, прежде чем предлагать заливку.
96
+
97
+ ### 6. Залей как НОВЫЙ черновик — никогда не перезаписывай существующее
98
+
99
+ Это главное отличие переноса из другого конструктора от обычной правки:
100
+ - **Всегда `create_graph` (новый `graphId`) или `create_graph_from_template`**, затем `update_graph`.
101
+ Либо `import_funnel(botId, name, graph)` — он сам делает create → update → dry-run → publish, но
102
+ publish из него всё равно требует подтверждения (см. п.7) — не вызывай `import_funnel` раньше, чем
103
+ пользователь согласился на публикацию, либо вызови и объясни, что опубликованный результат — это
104
+ черновик, который он должен подтвердить, ещё раз, прежде чем переключать его в активный.
105
+ - **Никогда не используй `edit_graph_live`, `patch_graph` или PUT в существующий `graphId`** для
106
+ результата переноса — это чужая, параллельно создаваемая воронка, а не правка уже работающего
107
+ сценария. Если у бота есть похожий/одноимённый старый граф — не трогай его.
108
+ - Назови новый граф так, чтобы было видно происхождение, например `"Перенос из BotHelp — <название>"`.
109
+ - Прогони `dry_run(graphId, kind:"command", value:"start")` (или подходящий триггер для IG) и
110
+ разбери `runStatus`.
111
+
112
+ ### 7. Публикуй только после подтверждения пользователя
113
+
114
+ Собранный и провалидированный черновик — не значит «готово к продакшену». Покажи пользователю: сколько
115
+ узлов/веток получилось, что промаплено с потерей (из п.3), что попало в `[ВСТАВЬ ТЕКСТ]`, ссылку/id
116
+ графа. **Публикуй (`publish_graph`) и переключай на активный (`set_active_graph`) только по явному
117
+ «ОК, публикуй» пользователя** — перенос почти всегда замещает текущую рабочую воронку бота, ошибка
118
+ здесь стоит дороже, чем при сборке воронки с нуля.
119
+
120
+ ### 8. (Опционально) перенос подписчиков
121
+
122
+ Перенос графа не переносит людей — это отдельный процесс через `migration_*` инструменты (CSV):
123
+ - `migration_presets()` — какие источники/пресеты колонок уже знает сервис (BotHelp, SaleBot, Senler,
124
+ Leadteh, Smartsender, ManyChat, универсальный CSV).
125
+ - `migration_preview(botId, path, platform, mapping?)` — без `mapping` вернёт подсказку сопоставления
126
+ колонок; с `mapping` — полный dry-run (ничего не пишет): сколько новых, сколько уже есть, нет ли
127
+ превышения лимита подписчиков тарифа.
128
+ - `migration_commit(botId, path, platform, mapping)` — реальный импорт тем же `mapping`, что
129
+ подтвердил `migration_preview`. Делай только после явного согласия пользователя, как и публикацию
130
+ графа.
131
+
132
+ Экспорт воронки (структуры сценария) ни один из этих сервисов документированно не отдаёт — поэтому
133
+ граф всегда собирается по п.1–7, а `migration_*` покрывает только список подписчиков (CSV).
134
+
135
+ ## Важные ограничения
136
+
137
+ - Если источник прислал JSON с кастомными типами блоков, которых нет у нас — **сначала** сверься со
138
+ schema.md, **потом** спрашивай пользователя (п.3). Никогда не придумывай тип узла по аналогии.
139
+ - Как и `build-bot-funnel`, при распознавании с приватной доски (Miro и т.п.) надёжнее всего
140
+ CSV-экспорт из неё самой, а не попытка распознать скриншот холста.
141
+ - Все лимиты и правила валидации графа (размер ≤4 МБ, длины текста, платные узлы/действия, лимиты
142
+ переменных) — те же, что в `build-bot-funnel`; не дублируются здесь.
143
+
144
+ ## Файлы скилла
145
+
146
+ - [reference/mapping.md](reference/mapping.md) — таблица сопоставления понятий (сообщения, кнопки,
147
+ задержки, теги, переменные, условия, оплаты, формы) с реальными узлами Retensy + ловушки по
148
+ конкретным источникам (BotHelp, SaleBot, Senler, Leadteh, Smartsender, ManyChat, PuzzleBot).
149
+ - [`../build-bot-funnel/reference/schema.md`](../build-bot-funnel/reference/schema.md) — формат графа
150
+ и единственный источник правды о типах узлов.
151
+ - [`../build-bot-funnel/reference/validation.md`](../build-bot-funnel/reference/validation.md) —
152
+ правила валидатора и коды ошибок.
153
+ - [`../build-bot-funnel/validate.mjs`](../build-bot-funnel/validate.mjs) — используй его же, отдельного
154
+ валидатора у этого скилла нет.