* feat(telegram): add CLI project with Bot API client * feat(telegram): add telegram-send plugin-tool * test(telegram): add unit tests for markdown, config, api * chore(env): add TELEGRAM_CHAT_ID to .env.example * docs(handoff): add handoff and ADR for telegram-send * docs(handoff): set PR number 174 * docs: update project map for telegram-send tool --------- Co-authored-by: opencode-agent <agent@opencode.local>
5.8 KiB
5.8 KiB
pr: 174 title: feat(telegram): add telegram-send plugin-tool for Bot API messaging
Что сделано
.opencode/telegram/— CLI-проект (по аналогии с.opencode/draw-image/):package.json—"type": "module", без runtime-deps; devDeps:@types/node,typescript,vitest; scripttest: vitest runtsconfig.json—target: ES2022,module: ESNext,moduleResolution: Bundler,strict,allowImportingTsExtensions: true,lib: ["ES2022", "DOM"](DOM нужен для типовfetch,BodyInit,Blob,FormData,File— все используются вsrc/api.ts)vitest.config.ts—environment: "node",include: ["tests/**/*.test.ts"](e2e.manual.ts исключён — нет.test.в имени)src/config.ts—loadConfig(overrides?): читаетTELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_IDиз env, валидирует, override chatId имеет приоритет над envsrc/markdown.ts—escapeMarkdownV2(text): экранирует спецсимволы_*[]()~\>#+-=|{}.!` (backslash НЕ в наборе), кириллицу не трогаетsrc/api.ts—sendMessage/sendDocument/sendPhotoчерез встроенныйfetchNode 22+;telegramFetchпарситresult, возвращает{ ok, message_id, chat_id }; приok:false— throw сdescriptioncli.ts— argv-парсер, диспетчер поaction(text/document/photo),parse_modeпо умолчанию"MarkdownV2"; успех →console.log(JSON.stringify(result)), провал → stderr +exit(1)
.opencode/tools/telegram-send.ts— plugin-tool обёртка (~35 строк):spawnSync("node", ["--experimental-strip-types", cliPath, ...cliArgs]), приstatus !== 0возвращает⚠️ telegram-send failed (exit N): ..., иначеstdout.trim()- Тесты в
.opencode/telegram/tests/(39 unit-тестов, все проходят):markdown.test.ts— 25 кейсов: пустая строка, без спецсимволов, кириллица, каждый спецсимволit.each, микс кириллицы+спецсимволов, все спецсимволы сразу, backtick,\не в набореconfig.test.ts— 5 кейсов: валидный env, отсутствие token, отсутствие chatId, override приоритет, override без envapi.test.ts— 9 кейсов: URL/method/body/headers sendMessage, parse_mode omit/plain/HTML, возвращаемое значение, Telegram API error, network error, sendDocument FormData, sendPhoto FormData (spy наFormData.prototype.append)e2e.manual.ts— ручной скрипт (НЕ вnpm test)
.env.example— добавленаTELEGRAM_CHAT_ID=your-telegram-chat-idв секцию# Telegram (optional)(послеTELEGRAM_BOT_TOKEN)
Почему
Оркестратору нужен быстрый способ показывать пользователю результаты работы — например, сгенерированный draw-image PNG или .md-файл с архитектурой проекта. Сейчас единственный способ показать файл — через tunnel skill (запуск процесса, проброс порта, ссылка), что тяжело для разовой отправки. Прямая отправка в Telegram через Bot API решает это одним вызовом tool'а: telegram-send({ action: "photo", path }) — и файл у пользователя.
Plugin-tool (не MCP-сервер) выбран как паттерн: авто-дискаверится opencode из .opencode/tools/, не требует записи в opencode.json, тонкая TS-обёртка дёргает CLI через spawnSync. Без runtime-зависимостей — только встроенный fetch/FormData/Blob Node 22+.
Pending
- Реальная E2E-проверка с живым ботом (нужны
TELEGRAM_BOT_TOKEN+TELEGRAM_CHAT_IDв env) —node --experimental-strip-types tests/e2e.manual.ts - Проверка цепочки
draw-image→telegram-send photoв реальном сценарии оркестратора - Возможно: интеграция
escapeMarkdownV2в оркестратор для авто-экранирования текста передaction: "text"
Watch out
parse_mode: "MarkdownV2"— формат по умолчанию вcli.ts:29, но НЕ вapi.ts(api-функции добавляютparse_modeв body только когда явно переданMarkdownV2/HTML;undefined/"plain"→ поле отсутствует). Это сознательно: api.ts — низкоуровневый, cli.ts — применяет дефолтlib: ["ES2022", "DOM"]вtsconfig.json— DOM нужен только для типовfetch/BodyInit/Blob/FormData/File; рантайм-значения берутся из Node 22+ глобалов (НЕ из браузера)allowImportingTsExtensions: true— сознательный паттерн проекта (какdraw-image), т.к. запуск черезnode --experimental-strip-typesс.ts-импортами- Node 22+ требуется (встроенные
fetch,Blob,FormData,Fileглобально) tests/e2e.manual.tsисключён изnpm testдвумя механизмами: отсутствие.test.в имени +include: ["tests/**/*.test.ts"]в vitest config — двойная защита- Plugin-tool НЕ MCP-сервер — НЕ добавлять в
opencode.json, авто-дискаверится из.opencode/tools/