* 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.1 KiB
ADR-073: telegram-send plugin-tool — Bot API messaging via CLI
Статус
Accepted (PR #174)
Контекст
Оркестратору нужен быстрый способ показывать пользователю результаты работы — сгенерированный draw-image PNG, .md-файл с архитектурой, текстовый вывод. Существующий способ показать файл — tunnel skill (запуск процесса + проброс порта через Cloudflare), что тяжело для разовой отправки и требует живого процесса.
В репо уже есть паттерн plugin-tool + CLI-проекта на примере draw-image: тонкая обёртка .opencode/tools/draw-image.ts дёргает CLI из .opencode/draw-image/cli.ts через spawnSync("node", ["--experimental-strip-types", cliPath, ...args]). Plugin-tools авто-дискаверятся opencode из папки .opencode/tools/ — без записи в opencode.json, без MCP-сервера.
Решение
Создан telegram-send plugin-tool + CLI-проект .opencode/telegram/ по паттерну draw-image:
-
Plugin-tool, не MCP-сервер —
.opencode/tools/telegram-send.tsавто-дискаверится opencode;spawnSyncзапускает CLI как дочерний процесс; результат парсится из stdout; ошибка →⚠️ telegram-send failed (exit N): <stderr>. Не требует записи вopencode.json, не требует long-running процесса. -
Без runtime-зависимостей —
package.jsonимеет только devDeps (@types/node,typescript,vitest). Рантайм использует встроенныйfetch/FormData/Blob/FileNode 22+. Это упрощает установку (толькоnpm installдля devDeps) и аудит. -
Один tool с параметром
action—action: "text" | "document" | "photo"вместо трёх отдельных tools. Причины: единый контрак ({ ok, message_id, chat_id }), один набор env-настроек, меньше шума в списке tools оркестратора. Параметрыpath/caption/textусловно-обязательные (зависят отaction). -
CLI как тонкий слой над api.ts —
cli.tsпарсит argv, применяетparse_modeпо умолчанию ("MarkdownV2"), диспетчеризует вsendMessage/sendDocument/sendPhoto.api.ts— низкоуровневый, не применяет дефолты (добавляетparse_modeв body только при явной передаче). Разделение: api.ts тестируется изолированно, cli.ts тестирует дефолты. -
lib: ["ES2022", "DOM"]в tsconfig — DOM-типы нужны только дляfetch/BodyInit/Blob/FormData/File(все используются вsrc/api.ts). Рантайм-значения берутся из Node 22+ глобалов, не из браузера.allowImportingTsExtensions: true— сознательный паттерн (какdraw-image) дляnode --experimental-strip-types.
Альтернативы
-
MCP-сервер для Telegram — отвергнуто: требует long-running процесса, записи в
opencode.json, сложнее отладка. Plugin-tool проще и следует существующему паттернуdraw-image. -
Прямой
fetchиз plugin-tool (без CLI) — отвергнуто: plugin-tool выполняется в Bun plugin-host (import.meta.dirдоступен), ноfetchтам может вести себя иначе; CLI-процесс на Node 22+ даёт предсказуемый рантайм с встроеннымfetch. Плюс CLI тестируется изолированно (npm test). -
Внешние зависимости (
node-telegram-bot-api,telegraf) — отвергнуто: добавляют audit-поверхность, усложняют установку, избыточны для 3 endpoint'ов (sendMessage,sendDocument,sendPhoto). Встроенныйfetch+FormDataдостаточны. -
Три отдельных tools (
telegram-send-text,telegram-send-document,telegram-send-photo) — отвергнуто: засоряет список tools, дублирует env-логику и контрак. Один tool сaction-enum чище. -
Markdown → MarkdownV2 авто-конвертер — отвергнуто (out-of-scope): пользователь сам экранирует через
escapeMarkdownV2(экспортируется изsrc/markdown.ts) или используетparse_mode: "plain"/"HTML". Конвертер — отдельная нетривиальная задача.