@mikitasazan/notify 1.19.0 → 1.21.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.
package/README.md CHANGED
@@ -91,8 +91,10 @@ notify report --project playhub --json < payload.json # весь объект
91
91
  Скобка у слова типа говорит одним словом, чем кончилось: `Deploy (OK)`,
92
92
  `CI (Fail)`, `Job (Off)`, `Job (Silent)`, `Issue (Assigned)`, `PR (Merged)`.
93
93
  Она стоит там, где у типа исходов больше одного. У `incident` состояние одно,
94
- поэтому его скобка называет МЕСТО, где горит: `Incident (Vault)`,
95
- `Incident (Session)` — слово из `--scope`, без него имя проекта. У `report`
94
+ поэтому его скобка называет МЕСТО, где горит: `Incident (vault)`,
95
+ `Incident (Session)` — слово из `--scope`, без него человеческое имя проекта
96
+ (таблица `DISPLAY` в `src/events.ts`; `vault` и `mac-config` — нарочно со
97
+ строчной, это машина и хранилище, а не бренды). У `report`
96
98
  исхода нет вовсе, и скобку занимает день: `Report (2026-08-23 / 2026-08-22)`.
97
99
 
98
100
  У `job` есть ещё два необязательных флага: `--via` — где задача крутилась
@@ -147,8 +149,9 @@ notify report --project playhub --json < payload.json # весь объект
147
149
  живому аккаунту, ботом это не сделать.
148
150
  2. `notify setup <chat_id> maphub` — заведёт вкладки «⚙️ Ops» и «💬 Dev» и
149
151
  напечатает готовую строку.
150
- 3. Вставить строку в `src/routes.ts` (и значение в тип `Project` в
151
- `src/events.ts`), выпустить пакет.
152
+ 3. Вставить строку в `src/routes.ts`, значение в тип `Project` и человеческое
153
+ имя в `DISPLAY` в `src/events.ts` (тест держит их в одном списке), выпустить
154
+ пакет.
152
155
  4. В репозиторий проекта — трёхстрочный `.github/workflows/notify.yml`,
153
156
  вызывающий `mikitasazan/notify/.github/workflows/ops-notify.yml@v1`:
154
157
  CI, деплой, PR и задачи начинают приходить сами.
package/dist/cli.js CHANGED
@@ -23,11 +23,12 @@
23
23
  * notify setup <forum chat_id> <project key> # create the tabs, see setup.ts
24
24
  */
25
25
  import { readFileSync } from 'node:fs';
26
+ import { DISPLAY } from "./events.js";
26
27
  import { KNOWN_FLAGS } from "./cli-flags.js";
27
28
  import { render, sessionTitle } from "./render.js";
28
29
  import { lintCard } from "./lint.js";
29
30
  import { notify } from "./send.js";
30
- import { ROUTES } from "./routes.js";
31
+ import { ROUTES, chatTitle } from "./routes.js";
31
32
  import { setupTopic } from "./setup.js";
32
33
  const log = (msg) => console.error(`[notify] ${msg}`);
33
34
  /**
@@ -58,7 +59,15 @@ if (command === 'lint-text') {
58
59
  process.exit(0);
59
60
  }
60
61
  if (command === 'routes') {
61
- process.stdout.write(`${JSON.stringify(ROUTES, null, 2)}\n`);
62
+ // `display`/`title` ride alongside the routing facts, not instead of them —
63
+ // every existing field stays. The avatar scripts and the name-drift check
64
+ // read `title` here rather than asking Telegram, so it must be `chatTitle()`
65
+ // verbatim, not a second guess at the same name.
66
+ const rows = Object.fromEntries(Object.keys(ROUTES).map((p) => [
67
+ p,
68
+ { ...ROUTES[p], display: DISPLAY[p], title: chatTitle(p) }
69
+ ]));
70
+ process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
62
71
  process.exit(0);
63
72
  }
64
73
  if (command === 'setup') {
package/dist/events.d.ts CHANGED
@@ -10,7 +10,22 @@
10
10
  * - required fields are never added — only a new event type.
11
11
  * That keeps old caller code and a new package compatible both ways.
12
12
  */
13
- export type Project = 'playhub' | 'one-q' | 'zabukai' | 'game-publisher' | 'vault' | 'mac-config' | 'alitools' | 'htmlg';
13
+ export type Project = 'playhub' | 'one-q' | 'zabukai' | 'game-publisher' | 'vault' | 'mac-config' | 'alitools' | 'htmlg' | '2roles';
14
+ /**
15
+ * The human name of a project, written exactly as the owner writes it.
16
+ * Source of truth is the project card index (`project-index` skill,
17
+ * `site-profiles.md`) in mac-config — a project key is a machine identifier
18
+ * and was never meant to be printed; `cap()` guessed at a name by capitalizing
19
+ * the key, which is how `htmlg` showed up as «Htmlg» on the one card that
20
+ * printed it (the Incident scope fallback, `render.ts`).
21
+ *
22
+ * `mac-config` and `vault` stay lowercase on purpose: a machine and a store,
23
+ * not brands — capitalizing them would invent a name the owner never uses.
24
+ *
25
+ * A Telegram chat title must equal `chatTitle()` in `routes.ts`, which is
26
+ * built out of this table.
27
+ */
28
+ export declare const DISPLAY: Record<Project, string>;
14
29
  /**
15
30
  * A stable machine key for the task — the instance tag on the FIRST line of
16
31
  * every card, shaped as `#key` (with no project name: the card already sits
package/dist/events.js CHANGED
@@ -10,6 +10,31 @@
10
10
  * - required fields are never added — only a new event type.
11
11
  * That keeps old caller code and a new package compatible both ways.
12
12
  */
13
+ /**
14
+ * The human name of a project, written exactly as the owner writes it.
15
+ * Source of truth is the project card index (`project-index` skill,
16
+ * `site-profiles.md`) in mac-config — a project key is a machine identifier
17
+ * and was never meant to be printed; `cap()` guessed at a name by capitalizing
18
+ * the key, which is how `htmlg` showed up as «Htmlg» on the one card that
19
+ * printed it (the Incident scope fallback, `render.ts`).
20
+ *
21
+ * `mac-config` and `vault` stay lowercase on purpose: a machine and a store,
22
+ * not brands — capitalizing them would invent a name the owner never uses.
23
+ *
24
+ * A Telegram chat title must equal `chatTitle()` in `routes.ts`, which is
25
+ * built out of this table.
26
+ */
27
+ export const DISPLAY = {
28
+ playhub: 'PlayHub',
29
+ 'one-q': 'One-Q',
30
+ zabukai: 'Zabukai',
31
+ 'game-publisher': 'Game Publisher',
32
+ vault: 'vault',
33
+ 'mac-config': 'mac-config',
34
+ alitools: 'Alitools',
35
+ htmlg: 'HTMLG',
36
+ '2roles': '2Roles'
37
+ };
13
38
  /** Red = with sound. Everything else is silent. (There is no separate "incidents" topic any more — an incident is visible in the project's feed.) */
14
39
  /**
15
40
  * The icon dictionary. Two laws, both set by the owner on 25.08.2026:
package/dist/render.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * separates BLOCKS BY MEANING (header / body / actions), not mechanically
20
20
  * after every line.
21
21
  */
22
- import { ICON, iconFor } from "./events.js";
22
+ import { DISPLAY, ICON, iconFor } from "./events.js";
23
23
  /**
24
24
  * A label gets a capital letter — but NOT a name that is deliberately
25
25
  * written lowercase: `iOS` was turning into `IOS`. The signal is a capital
@@ -971,12 +971,14 @@ const renderIncident = (e) => {
971
971
  const findings = bullets(e.items, false);
972
972
  return join([
973
973
  // The bracket on an incident names WHERE it burns, not how it ended —
974
- // it has one ending. `Incident (Vault):`, `Incident (Session):`; with no
975
- // word from the sender, the project name stands in.
974
+ // it has one ending. `Incident (vault):`, `Incident (Session):`; with no
975
+ // word from the sender, the project's own DISPLAY name stands in — the
976
+ // owner's human spelling, not `cap()`'s guess at one (`cap('htmlg')` gave
977
+ // «Htmlg», the one place a project key ever reached a human eye).
976
978
  // `filled`, not `??`: `scope: ''` and `scope: ' '` are a sender that
977
979
  // named no place, and `??` let the first past the project fallback (no
978
980
  // bracket at all) and the second into a visibly empty one.
979
- typeLine(iconFor(e), 'Incident', e.title, e.url, filled(e.scope) ?? cap(e.project)),
981
+ typeLine(iconFor(e), 'Incident', e.title, e.url, filled(e.scope) ?? DISPLAY[e.project]),
980
982
  e.detail && e.detail !== e.title ? note(e.detail) : null,
981
983
  // The rows below came from the `session` card when it was folded in
982
984
  // (03.09.2026), in the order they had there: which copy, what the guard
package/dist/routes.d.ts CHANGED
@@ -44,6 +44,14 @@ type Forum = {
44
44
  dev?: number;
45
45
  };
46
46
  export declare const ROUTES: Record<Project, Forum>;
47
+ /**
48
+ * A solo project's chat is «<Name> · Ops»; a team forum is just the name — the
49
+ * forum already carries its own «Ops»/«Dev» tabs, so the chat's own title
50
+ * does not need to repeat the word. The real Telegram chat title and this
51
+ * function must agree: the avatar scripts and the name-drift check both read
52
+ * it through `notify routes --json`, not by asking Telegram.
53
+ */
54
+ export declare const chatTitle: (p: Project) => string;
47
55
  export type Target = {
48
56
  chat: string;
49
57
  thread?: number;
package/dist/routes.js CHANGED
@@ -1,32 +1,38 @@
1
- import { severity } from "./events.js";
1
+ import { DISPLAY, severity } from "./events.js";
2
2
  export const ROUTES = {
3
- // ops/dev = 22/23, not 3/4: the old tabs were deleted by hand on
4
- // 27.07.2026, and Telegram removes a topic's messages along with it. A
5
- // recreated topic gets a NEW id — a topic's id is the id of its first
6
- // message, it is never reused.
3
+ // The only project with a team, and so the only forum left. ops/dev =
4
+ // 22/23, not 3/4: the old tabs were deleted by hand on 27.07.2026, and
5
+ // Telegram removes a topic's messages along with it. A recreated topic
6
+ // gets a NEW id — a topic's id is the id of its first message, it is
7
+ // never reused.
7
8
  zabukai: { chat: '-1004299939100', ops: 22, dev: 23 },
8
- playhub: { chat: '-1004418379613', ops: 3, dev: 4 },
9
- 'game-publisher': { chat: '-1004292453693', ops: 3, dev: 4 },
10
- 'one-q': { chat: '-1004466909784', ops: 3, dev: 4 },
11
- // No `dev`: the safe is infrastructure, there is no one to discuss it
12
- // with. Only reports from the weekly check, and only when something
13
- // broke.
14
- vault: { chat: '-1004459314999', ops: 3 },
15
- // Also infrastructure, no `dev`: the daily task digest and the Monday
16
- // stumbles summary. Before this row, both used to die silently on
17
- // "unknown project."
18
- 'mac-config': { chat: '-1004442522004', ops: 2 },
19
- // The owner's corporate project: only its own reports on "what the
20
- // publish delivered" (ali98x-sentry). No `dev` — the project's team lives
21
- // in other systems, there are no people here. Until 18.08.2026 reports
22
- // were getting lost on "unknown project" for weeks.
23
- alitools: { chat: '-1003904331479', ops: 3 },
24
- // Solo project, plain supergroup without topics (owner's rule 05.09.2026):
25
- // no `ops`, no `dev`, the bot posts into the chat itself. Created
26
- // 05.09.2026 as «HTMLG · Ops» (HTMLG is the HTML5-games business; the brand
27
- // and domain of its portal may change, the project name does not).
28
- htmlg: { chat: '-1004334723487' }
9
+ // Every row below is a project the owner runs alone. Their Topics were
10
+ // turned off on 06.09.2026 and their `ops` numbers went with them: a card
11
+ // now goes to the chat itself. Sending a thread id into a chat that is no
12
+ // longer a forum fails with "message thread not found", so a row here and
13
+ // the chat's own setting must always agree.
14
+ playhub: { chat: '-1004418379613' },
15
+ 'game-publisher': { chat: '-1004292453693' },
16
+ 'one-q': { chat: '-1004466909784' },
17
+ vault: { chat: '-1004459314999' },
18
+ 'mac-config': { chat: '-1004442522004' },
19
+ alitools: { chat: '-1003904331479' },
20
+ // Created 05.09.2026 as «HTMLG · Ops», the first chat built this way
21
+ // (HTMLG is the HTML5-games business; the brand and domain of its portal
22
+ // may change, the project name does not).
23
+ htmlg: { chat: '-1004334723487' },
24
+ // Created 08.09.2026 as «2Roles · Ops»: the third focus project, the owner
25
+ // runs it alone.
26
+ '2roles': { chat: '-1004314188744' }
29
27
  };
28
+ /**
29
+ * A solo project's chat is «<Name> · Ops»; a team forum is just the name — the
30
+ * forum already carries its own «Ops»/«Dev» tabs, so the chat's own title
31
+ * does not need to repeat the word. The real Telegram chat title and this
32
+ * function must agree: the avatar scripts and the name-drift check both read
33
+ * it through `notify routes --json`, not by asking Telegram.
34
+ */
35
+ export const chatTitle = (p) => ROUTES[p].ops === undefined ? `${DISPLAY[p]} · Ops` : DISPLAY[p];
30
36
  /**
31
37
  * Where an event goes. Everything goes to its own project's "Ops" tab; a
32
38
  * red one arrives there too, just with sound.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikitasazan/notify",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Единая типизированная отправка Telegram-уведомлений (форум-темы, маршрутизация, ретраи) для всех проектов",
5
5
  "type": "module",
6
6
  "license": "MIT",