npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mikitasazan/notify

v1.16.0

Published

Единая типизированная отправка Telegram-уведомлений (форум-темы, маршрутизация, ретраи) для всех проектов

Readme

@mikitasazan/notify

Единая типизированная отправка Telegram-уведомлений: форум на проект, внутри вкладки «⚙️ Ops» (роботы) и «💬 Dev» (люди), один бот, семь типов событий. Заменяет 18 разных имён переменных и 4+ независимых реализации jq | curl, что были в playhub, arvent, game-publisher до 26.07.2026.

Почему форум на проект, а не один общий форум с темой на проект. Второе пробовали первым — не работает: Telegram не умеет закрывать отдельную тему от участника, кто в группе, видит все вкладки. Значит сотрудников в общий форум не пустить, им нужен свой чат — и проект начинает жить в двух местах сразу («тема у владельца» + «канал у команды»). Форум на проект убирает дублирование: сотрудник добавляется в форум своего проекта, чужих не видит, схема одна для всех.

Идея в одном абзаце

notify() принимает объект (NotifyEvent), а не строку — «своё» сообщение написать нельзя. Один рендерер на тип события гарантирует одинаковый каркас: эмодзи Заголовок · проект, затем ключ: значение, затем ссылка. Маршрут «куда слать» вычисляется из --project и severity события — добавить новый проект значит дописать одну строку в src/routes.ts, больше нигде ничего заводить не нужно.

Ноль рантайм-зависимостей. Composite-action и консьюмеры не собирают ничего: Node 22.18+ / 24+ исполняет .ts напрямую (проверено на macOS и на проде playhub, Node 22.22.2), в GitHub Actions ставить нечего. Релиз пакета в npm собирается в dist (preparetsc) — поэтому npm-консьюмеры получают скомпилированный JS и типы.

Установка

npm install @mikitasazan/notify

Секрет один — OPS_BOT_TOKEN. Взять из vault (vault get notify.OPS_BOT_TOKEN) или из GitHub secrets репозитория.

Из TypeScript

import { notify } from '@mikitasazan/notify';

await notify({
  type: 'report',
  project: 'playhub',
  title: 'Сводка за день',
  period: '26 июля',
  lines: [['Игр в каталоге', 1284], ['Ошибок PM2', 3]]
});

Из bash (сервер, cron, ноутбук)

notify job --project playhub --job "Импорт игр" --status ok --stat "добавлено=5"
trap 'notify job --project playhub --job "Импорт игр" --status fail --note "лог: $LOG"' ERR
notify report --project playhub --json < payload.json   # весь объект со stdin

Код возврата всегда 0 — уведомление не имеет права уронить вызвавший его процесс. Все ошибки — в stderr.

Из GitHub Actions

- uses: mikitasazan/[email protected]   # версию пинит propagate
  if: always()
  with: { event: deploy, project: playhub, status: '${{ job.status }}' }
  env:  { OPS_BOT_TOKEN: '${{ secrets.OPS_BOT_TOKEN }}' }

status: ${{ job.status }} (success/failure) автоматически маппится в ok/fail — не нужно двух шагов с if: success()/if: failure().

Типы событий

| Тип | Когда | Обязательные поля | |---|---|---| | deploy | выкатка кода | project, status | | job | регулярная задача (импорт, бэкап) | project, job, status | | report | сводка с цифрами | project, title, lines | | ci | итог CI на master | project, status | | pr | событие пул-реквеста | project, action, number, title | | issue | событие задачи | project, action, number, title | | incident | что-то встало и ждёт тебя прямо сейчас: приложение, сейф, рабочая сессия на маке | project, title | | session | УСТАРЕЛ, это incident: карточка выходит с тегом #incident, имя собирается из actionClaude session is burning the limit | project, action | | heartbeat_miss | УСТАРЕЛ, молчание — это job --status silent | project, job |

Скобка у слова типа говорит одним словом, чем кончилось: Deploy (OK), CI (Fail), Job (Off), Job (Silent), Issue (Assigned), PR (Merged). Она стоит там, где у типа исходов больше одного. У incident состояние одно, поэтому его скобка называет МЕСТО, где горит: Incident (Vault), Incident (Session) — слово из --scope, без него имя проекта. У report исхода нет вовсе, и скобку занимает день: Report (2026-08-23 / 2026-08-22).

У job есть ещё два необязательных флага: --via — где задача крутилась (mac, vps, actions), это идёт строкой Via: Mac первой под второй строкой; и --took — сколько прогон занял, твоими же словами (4m 12s), строкой Took: под причиной. Не передал — строки нет.

Отдельного вида «файл» нет: --path применим к ЛЮБОМУ событию, и тогда карточка едет подписью к вложению (подпись у Telegram ограничена 1024 знаками, а не 4000). Слово file осталось псевдонимом и собирает report с вложением — старый отправитель не замолкает.

Полные сигнатуры — src/events.ts.

Ключ задачи. Первая строка каждой карточки — три тега: #тип #ключ #итог. По ним дневной разборщик сверяет «это 🔴 уже закрыто более поздней карточкой той же задачи?» без сравнения человеческих формулировок. Явный --key побеждает; без него ключ выводится из заголовка и меняется вместе с ним — регулярный отправитель передаёт --key явно. В stdout CLI ключ не попадает.

Свободного HTML в пакете нет. Дверь sendReport() удалена 25.08.2026: она отдавала произвольную разметку и ставила карточке два тега вместо трёх — единственный вид, который нельзя было отфильтровать по итогу. Последний её отправитель перешёл на типизированный report ещё 25.08; тип события задаёт каждый знак карточки, других путей нет.

Неизвестный проект не роняет вызвавший крон (код возврата 0), но больше и не исчезает молча: в mac-config Ops уходит красная карточка «notify: событие потеряно». До 18.08.2026 опечатка в --project терялась без следа неделями.

--action у pr: opened, approved, changes_requested, merged, closed. У issue: opened, assigned, closed. Сырые имена GitHub, которые означают то же самое, сводятся к ним: reopened, ready_for_review и review_requested — это всё opened. Неизвестное действие — ошибка разбора, а не молчаливая подмена: иначе «запрошены правки» приехали бы как «открыт».

Из GitHub в «⚙️ Ops» уходит не всё, что там происходит. Общий workflow .github/workflows/ops-notify.yml отбрасывает ready_for_review и review_requested ещё на входе: оба сообщают про PR, который уже объявлен открытым, и открытие одного PR давало три карточки подряд. Правило владельца от 27.07.2026 — одна новость, одна карточка: открыт → вердикт ревью (👍 / 📝) → закрыт или смёржен. Белый список живёт в общем workflow, а не в подписках проектов, потому что подписка обязана лежать в вызывающем репозитории и в четырёх проектах расходится сама собой; политика же должна раскатываться одним тегом.

Новый проект

  1. В Telegram: создать группу, открыть её → «Изменить» → включить «Темы», добавить @mikita_ops_bot администратором с правом «Управление темами». Это единственный ручной шаг — Telegram разрешает создавать группы только живому аккаунту, ботом это не сделать.
  2. notify setup <chat_id> maphub — заведёт вкладки «⚙️ Ops» и «💬 Dev» и напечатает готовую строку.
  3. Вставить строку в src/routes.ts (и значение в тип Project в src/events.ts), выпустить пакет.
  4. В репозиторий проекта — трёхстрочный .github/workflows/notify.yml, вызывающий mikitasazan/notify/.github/workflows/ops-notify.yml@v1: CI, деплой, PR и задачи начинают приходить сами.
  5. Закрепить в «⚙️ Ops» легенду значков (пример — в любом существующем форуме) и, если задача проекта бежит с мака, — добавить её плист под обёртку run-scheduled.sh (репозиторий mac-config).

Весь путь — минут десять, единственный ручной шаг — п. 1.

Появились сотрудники на проекте — просто добавь их в форум этого проекта. Ничего не мигрируется и не дублируется: они видят Ops и Dev своего проекта и не видят остальных.

Эволюция схемы событий

Версии схемы нет и не будет. Сообщение живёт секунду и читается глазами — версионировать нечего. Вместо этого одно правило: новое поле у существующего типа — только опциональное; обязательные поля не добавляются никогда, только новый тип события. Тогда старый вызывающий код и новый пакет всегда совместимы.

Тег сменился 18.08.2026 у шести отправителей

Коммит 85564fc в mac-config дал явный --key шести местам вызова, которые раньше отправляли карточку БЕЗ ключа — тег брался из --job/--title автоматически (slug(), см. src/render.ts). Без этой таблицы старую красную карточку в Telegram не найти поиском по новому тегу — она там под старым.

| Отправитель | Старый тег (авто из русского названия) | Новый тег (из --key) | |---|---|---| | arvent-eval-report.sh, отчёт по эвалу | #eval_качество_ответов_бота | #arvent_eval | | arvent-eval-report.sh, файл диалогов | нет — шёл мимо пакета, сырым sendDocument Bot API | #arvent_eval_dialogues | | daily-digest.sh, дайджест задач (job) | #дайджест_задач | #daily_digest | | daily-digest.sh, дайджест задач (report) | #дайджест_задач (то же слово, другой тип-тег: #report вместо #job) | #daily_digest | | daily-digest.sh, отключённые Actions | #github_actions_выключены | #actions_off | | daily-digest.sh, Config doctor | #config_doctor | #config_doctor — не изменился: slug() приводит и старое, и новое к одной строке |

Чего в пакете нет и почему

  • Очереди, брокера, демона — десятки сообщений в день, ретрай в памяти процесса; доставка уведомления не стоит инфраструктуры, которую саму надо мониторить.
  • Редактирования уже отправленных сообщений — требует хранить message_id, то есть состояние и базу.
  • Автосоздания темы при первой отправке — сбой мог бы наплодить дублей; создание — явная команда notify setup.
  • Любых зависимостей, сборки, MarkdownV2, мультиязычности, троттла (троттл — забота вызывающего кода, он есть в arvent/web/src/server/alert.ts и там и остаётся: работает только внутри долгоживущего процесса, а CLI стартует заново на каждый вызов).

Разработка

npm run typecheck   # tsc --noEmit
npm test            # node --test src/render.test.ts

Release

Один ритуал для всех общих пакетов (канон — скилл package-ops):

release patch|minor|major   # из этого каталога

Тесты → бамп + тег → публикация из клона тега → точная версия у всех потребителей с прогоном их собственных проверок, включая пин uses: в их workflow. release и propagate живут в mac-config/home/bin/ и доступны из PATH. Карты потребителей нет: propagate находит их поиском по репозиториям.