@cogenta/channels 0.1.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.
Files changed (177) hide show
  1. package/dist/adapter.d.ts +111 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +2 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/approvals/commands.d.ts +28 -0
  6. package/dist/approvals/commands.d.ts.map +1 -0
  7. package/dist/approvals/commands.js +66 -0
  8. package/dist/approvals/commands.js.map +1 -0
  9. package/dist/approvals/dispatch.d.ts +22 -0
  10. package/dist/approvals/dispatch.d.ts.map +1 -0
  11. package/dist/approvals/dispatch.js +22 -0
  12. package/dist/approvals/dispatch.js.map +1 -0
  13. package/dist/approvals/message.d.ts +18 -0
  14. package/dist/approvals/message.d.ts.map +1 -0
  15. package/dist/approvals/message.js +40 -0
  16. package/dist/approvals/message.js.map +1 -0
  17. package/dist/approvals/signed-link.d.ts +19 -0
  18. package/dist/approvals/signed-link.d.ts.map +1 -0
  19. package/dist/approvals/signed-link.js +42 -0
  20. package/dist/approvals/signed-link.js.map +1 -0
  21. package/dist/approvals/store.d.ts +41 -0
  22. package/dist/approvals/store.d.ts.map +1 -0
  23. package/dist/approvals/store.js +41 -0
  24. package/dist/approvals/store.js.map +1 -0
  25. package/dist/approvals/token.d.ts +7 -0
  26. package/dist/approvals/token.d.ts.map +1 -0
  27. package/dist/approvals/token.js +40 -0
  28. package/dist/approvals/token.js.map +1 -0
  29. package/dist/formats/alert.d.ts +18 -0
  30. package/dist/formats/alert.d.ts.map +1 -0
  31. package/dist/formats/alert.js +49 -0
  32. package/dist/formats/alert.js.map +1 -0
  33. package/dist/formats/budget.d.ts +14 -0
  34. package/dist/formats/budget.d.ts.map +1 -0
  35. package/dist/formats/budget.js +13 -0
  36. package/dist/formats/budget.js.map +1 -0
  37. package/dist/formats/notification.d.ts +8 -0
  38. package/dist/formats/notification.d.ts.map +1 -0
  39. package/dist/formats/notification.js +26 -0
  40. package/dist/formats/notification.js.map +1 -0
  41. package/dist/formats/report.d.ts +18 -0
  42. package/dist/formats/report.d.ts.map +1 -0
  43. package/dist/formats/report.js +44 -0
  44. package/dist/formats/report.js.map +1 -0
  45. package/dist/inbound/authorize.d.ts +57 -0
  46. package/dist/inbound/authorize.d.ts.map +1 -0
  47. package/dist/inbound/authorize.js +32 -0
  48. package/dist/inbound/authorize.js.map +1 -0
  49. package/dist/inbound/router.d.ts +67 -0
  50. package/dist/inbound/router.d.ts.map +1 -0
  51. package/dist/inbound/router.js +69 -0
  52. package/dist/inbound/router.js.map +1 -0
  53. package/dist/index.d.ts +77 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +42 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/linking/codes.d.ts +6 -0
  58. package/dist/linking/codes.d.ts.map +1 -0
  59. package/dist/linking/codes.js +37 -0
  60. package/dist/linking/codes.js.map +1 -0
  61. package/dist/linking/store.d.ts +39 -0
  62. package/dist/linking/store.d.ts.map +1 -0
  63. package/dist/linking/store.js +77 -0
  64. package/dist/linking/store.js.map +1 -0
  65. package/dist/linking/tables.d.ts +14 -0
  66. package/dist/linking/tables.d.ts.map +1 -0
  67. package/dist/linking/tables.js +52 -0
  68. package/dist/linking/tables.js.map +1 -0
  69. package/dist/preferences/dispatcher.d.ts +37 -0
  70. package/dist/preferences/dispatcher.d.ts.map +1 -0
  71. package/dist/preferences/dispatcher.js +103 -0
  72. package/dist/preferences/dispatcher.js.map +1 -0
  73. package/dist/preferences/quiet-hours.d.ts +12 -0
  74. package/dist/preferences/quiet-hours.d.ts.map +1 -0
  75. package/dist/preferences/quiet-hours.js +22 -0
  76. package/dist/preferences/quiet-hours.js.map +1 -0
  77. package/dist/preferences/store.d.ts +8 -0
  78. package/dist/preferences/store.d.ts.map +1 -0
  79. package/dist/preferences/store.js +60 -0
  80. package/dist/preferences/store.js.map +1 -0
  81. package/dist/preferences/tables.d.ts +12 -0
  82. package/dist/preferences/tables.d.ts.map +1 -0
  83. package/dist/preferences/tables.js +55 -0
  84. package/dist/preferences/tables.js.map +1 -0
  85. package/dist/preferences/types.d.ts +37 -0
  86. package/dist/preferences/types.d.ts.map +1 -0
  87. package/dist/preferences/types.js +31 -0
  88. package/dist/preferences/types.js.map +1 -0
  89. package/dist/providers/discord/adapter.d.ts +38 -0
  90. package/dist/providers/discord/adapter.d.ts.map +1 -0
  91. package/dist/providers/discord/adapter.js +97 -0
  92. package/dist/providers/discord/adapter.js.map +1 -0
  93. package/dist/providers/discord/client.d.ts +67 -0
  94. package/dist/providers/discord/client.d.ts.map +1 -0
  95. package/dist/providers/discord/client.js +65 -0
  96. package/dist/providers/discord/client.js.map +1 -0
  97. package/dist/providers/discord/gateway.d.ts +55 -0
  98. package/dist/providers/discord/gateway.d.ts.map +1 -0
  99. package/dist/providers/discord/gateway.js +114 -0
  100. package/dist/providers/discord/gateway.js.map +1 -0
  101. package/dist/providers/discord/inbound.d.ts +22 -0
  102. package/dist/providers/discord/inbound.d.ts.map +1 -0
  103. package/dist/providers/discord/inbound.js +75 -0
  104. package/dist/providers/discord/inbound.js.map +1 -0
  105. package/dist/providers/discord/render.d.ts +9 -0
  106. package/dist/providers/discord/render.d.ts.map +1 -0
  107. package/dist/providers/discord/render.js +73 -0
  108. package/dist/providers/discord/render.js.map +1 -0
  109. package/dist/providers/email/adapter.d.ts +18 -0
  110. package/dist/providers/email/adapter.d.ts.map +1 -0
  111. package/dist/providers/email/adapter.js +41 -0
  112. package/dist/providers/email/adapter.js.map +1 -0
  113. package/dist/providers/email/file-transport.d.ts +14 -0
  114. package/dist/providers/email/file-transport.d.ts.map +1 -0
  115. package/dist/providers/email/file-transport.js +33 -0
  116. package/dist/providers/email/file-transport.js.map +1 -0
  117. package/dist/providers/email/render.d.ts +20 -0
  118. package/dist/providers/email/render.d.ts.map +1 -0
  119. package/dist/providers/email/render.js +89 -0
  120. package/dist/providers/email/render.js.map +1 -0
  121. package/dist/providers/email/transport.d.ts +25 -0
  122. package/dist/providers/email/transport.d.ts.map +1 -0
  123. package/dist/providers/email/transport.js +2 -0
  124. package/dist/providers/email/transport.js.map +1 -0
  125. package/dist/providers/slack/adapter.d.ts +35 -0
  126. package/dist/providers/slack/adapter.d.ts.map +1 -0
  127. package/dist/providers/slack/adapter.js +91 -0
  128. package/dist/providers/slack/adapter.js.map +1 -0
  129. package/dist/providers/slack/client.d.ts +45 -0
  130. package/dist/providers/slack/client.d.ts.map +1 -0
  131. package/dist/providers/slack/client.js +54 -0
  132. package/dist/providers/slack/client.js.map +1 -0
  133. package/dist/providers/slack/inbound.d.ts +23 -0
  134. package/dist/providers/slack/inbound.d.ts.map +1 -0
  135. package/dist/providers/slack/inbound.js +76 -0
  136. package/dist/providers/slack/inbound.js.map +1 -0
  137. package/dist/providers/slack/render.d.ts +8 -0
  138. package/dist/providers/slack/render.d.ts.map +1 -0
  139. package/dist/providers/slack/render.js +72 -0
  140. package/dist/providers/slack/render.js.map +1 -0
  141. package/dist/providers/slack/socket.d.ts +34 -0
  142. package/dist/providers/slack/socket.d.ts.map +1 -0
  143. package/dist/providers/slack/socket.js +70 -0
  144. package/dist/providers/slack/socket.js.map +1 -0
  145. package/dist/providers/telegram/adapter.d.ts +35 -0
  146. package/dist/providers/telegram/adapter.d.ts.map +1 -0
  147. package/dist/providers/telegram/adapter.js +109 -0
  148. package/dist/providers/telegram/adapter.js.map +1 -0
  149. package/dist/providers/telegram/client.d.ts +78 -0
  150. package/dist/providers/telegram/client.d.ts.map +1 -0
  151. package/dist/providers/telegram/client.js +53 -0
  152. package/dist/providers/telegram/client.js.map +1 -0
  153. package/dist/providers/telegram/inbound.d.ts +21 -0
  154. package/dist/providers/telegram/inbound.d.ts.map +1 -0
  155. package/dist/providers/telegram/inbound.js +72 -0
  156. package/dist/providers/telegram/inbound.js.map +1 -0
  157. package/dist/providers/telegram/render.d.ts +15 -0
  158. package/dist/providers/telegram/render.d.ts.map +1 -0
  159. package/dist/providers/telegram/render.js +87 -0
  160. package/dist/providers/telegram/render.js.map +1 -0
  161. package/dist/providers/webhook/adapter.d.ts +40 -0
  162. package/dist/providers/webhook/adapter.d.ts.map +1 -0
  163. package/dist/providers/webhook/adapter.js +71 -0
  164. package/dist/providers/webhook/adapter.js.map +1 -0
  165. package/dist/providers/webhook/render.d.ts +26 -0
  166. package/dist/providers/webhook/render.d.ts.map +1 -0
  167. package/dist/providers/webhook/render.js +41 -0
  168. package/dist/providers/webhook/render.js.map +1 -0
  169. package/dist/providers/webhook/signing.d.ts +59 -0
  170. package/dist/providers/webhook/signing.d.ts.map +1 -0
  171. package/dist/providers/webhook/signing.js +108 -0
  172. package/dist/providers/webhook/signing.js.map +1 -0
  173. package/dist/registry.d.ts +16 -0
  174. package/dist/registry.d.ts.map +1 -0
  175. package/dist/registry.js +39 -0
  176. package/dist/registry.js.map +1 -0
  177. package/package.json +42 -0
@@ -0,0 +1,75 @@
1
+ const CHANNEL_NAME = 'discord';
2
+ const MESSAGE_COMPONENT = 3;
3
+ /**
4
+ * The real routing/authorization wiring for one Discord dispatch event —
5
+ * extracted from the adapter, same shape as `providers/slack/inbound.ts`.
6
+ * Handles both a typed message (`MESSAGE_CREATE`) and a button press
7
+ * (`INTERACTION_CREATE` with `type: MESSAGE_COMPONENT`, whose `custom_id` is
8
+ * itself a command string rendered by `render.ts`) through the exact same
9
+ * `CommandRouter.route()`, never a second, parallel authorization path.
10
+ */
11
+ export function createDiscordInboundHandler(deps) {
12
+ async function resolveIdentity(channelUserId) {
13
+ return deps.linkStore.resolveIdentity(CHANNEL_NAME, channelUserId);
14
+ }
15
+ async function reply(channelId, content) {
16
+ await deps.client.sendMessage({ channelId, content });
17
+ }
18
+ async function handleText(channelId, channelUserId, text) {
19
+ let identity = await resolveIdentity(channelUserId);
20
+ if (identity.linkedUserId === null) {
21
+ // An unlinked identity gets exactly one thing to try: the message AS
22
+ // a linking code. Any other failure stays silent — "Une identité de
23
+ // canal non liée à un compte est ignorée, sans réponse."
24
+ try {
25
+ await deps.linkStore.verifyCode(text.trim(), CHANNEL_NAME, channelUserId);
26
+ identity = await resolveIdentity(channelUserId);
27
+ await reply(channelId, '✅ Compte lié. Vous pouvez maintenant utiliser les commandes.');
28
+ }
29
+ catch {
30
+ // Wrong/expired/nonexistent code from an unlinked identity: silence.
31
+ }
32
+ const handler = deps.getExternalHandler();
33
+ if (handler !== undefined)
34
+ await handler({ text, identity });
35
+ return;
36
+ }
37
+ const handler = deps.getExternalHandler();
38
+ if (handler !== undefined)
39
+ await handler({ text, identity });
40
+ const result = await deps.router.route(text, identity);
41
+ if (!result.shouldReply)
42
+ return;
43
+ if (result.kind === 'forbidden') {
44
+ await reply(channelId, "⛔ Vous n'avez pas la permission d'exécuter cette commande.");
45
+ }
46
+ else if (result.kind === 'unrecognized') {
47
+ await reply(channelId, `Commande inconnue : ${result.commandName}`);
48
+ }
49
+ }
50
+ return async function handleDispatch(event) {
51
+ if (event.type === 'MESSAGE_CREATE') {
52
+ const data = event.data;
53
+ if (data.author.bot === true)
54
+ return;
55
+ await handleText(data.channel_id, data.author.id, data.content);
56
+ return;
57
+ }
58
+ if (event.type === 'INTERACTION_CREATE') {
59
+ const data = event.data;
60
+ if (data.type !== MESSAGE_COMPONENT)
61
+ return;
62
+ const channelUserId = data.member?.user.id ?? data.user?.id;
63
+ const customId = data.data?.custom_id;
64
+ if (channelUserId === undefined || customId === undefined || data.channel_id === undefined) {
65
+ return;
66
+ }
67
+ // Discord requires an ack within 3s or the component shows "This
68
+ // interaction failed" — a deferred update, no visible change; any
69
+ // real reply is a normal follow-up message, same as the text path.
70
+ await deps.client.acknowledgeInteraction(data.id, data.token);
71
+ await handleText(data.channel_id, channelUserId, customId);
72
+ }
73
+ };
74
+ }
75
+ //# sourceMappingURL=inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../../src/providers/discord/inbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ChannelMessage } from '../../adapter.js';
2
+ import type { DiscordActionRow, DiscordEmbed } from './client.js';
3
+ export interface RenderedDiscordMessage {
4
+ readonly content?: string;
5
+ readonly embeds: readonly DiscordEmbed[];
6
+ readonly components: readonly DiscordActionRow[];
7
+ }
8
+ export declare function renderDiscordMessage(message: ChannelMessage): RenderedDiscordMessage;
9
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/providers/discord/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAmB,MAAM,kBAAkB,CAAA;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAuCjE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAA;CACjD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,sBAAsB,CAyCpF"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * "On n'écrit pas de Markdown Telegram dans le code métier" applies just as
3
+ * much to Discord's own embed format — everything platform-specific lives
4
+ * in this module, `ChannelMessage` never knows embeds exist.
5
+ */
6
+ const SEVERITY_COLOR = {
7
+ info: 0x5865f2, // Discord blurple
8
+ warning: 0xfaa61a,
9
+ critical: 0xed4245,
10
+ };
11
+ const SEVERITY_PREFIX = {
12
+ info: 'ℹ️',
13
+ warning: '⚠️',
14
+ critical: '🔴',
15
+ };
16
+ function renderActionsRow(actions) {
17
+ if (actions === undefined || actions.length === 0)
18
+ return undefined;
19
+ return {
20
+ type: 1,
21
+ components: actions.map((action) => ({
22
+ type: 2,
23
+ style: 1,
24
+ label: action.label,
25
+ // `custom_id` is the command text a component interaction routes as
26
+ // (mirrors Telegram's `callback_data`, Slack's `action_id`/`value`) —
27
+ // the same `CommandRouter.route()`, never a second, parallel
28
+ // authorization path.
29
+ custom_id: action.id,
30
+ })),
31
+ };
32
+ }
33
+ export function renderDiscordMessage(message) {
34
+ if (message.level === 'notification') {
35
+ return { content: message.text, embeds: [], components: [] };
36
+ }
37
+ if (message.level === 'alert') {
38
+ const prefix = SEVERITY_PREFIX[message.severity];
39
+ const embed = {
40
+ title: `${prefix} ${message.title}`,
41
+ description: message.context,
42
+ url: message.adminUrl,
43
+ color: SEVERITY_COLOR[message.severity],
44
+ fields: [
45
+ { name: 'Action attendue', value: message.expectedAction },
46
+ { name: 'Admin', value: `[Voir dans l'admin](${message.adminUrl})` },
47
+ ],
48
+ };
49
+ const actionsRow = renderActionsRow(message.actions);
50
+ return { embeds: [embed], components: actionsRow === undefined ? [] : [actionsRow] };
51
+ }
52
+ // 'report'
53
+ const fields = [
54
+ ...message.keyFigures.map((figure) => ({
55
+ name: figure.label,
56
+ value: figure.value,
57
+ inline: true,
58
+ })),
59
+ ...message.sections.map((section) => ({
60
+ name: section.heading ?? 'Détail',
61
+ value: section.body,
62
+ })),
63
+ ];
64
+ const embed = {
65
+ title: message.title,
66
+ ...(fields.length > 0 ? { fields } : {}),
67
+ ...(message.moreUrl === undefined
68
+ ? {}
69
+ : { description: `[Voir le détail](${message.moreUrl})` }),
70
+ };
71
+ return { embeds: [embed], components: [] };
72
+ }
73
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/providers/discord/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { ChannelAdapter } from '../../adapter.js';
2
+ import { type EmailActionLinkOptions } from './render.js';
3
+ import type { EmailTransport } from './transport.js';
4
+ export interface EmailAdapterOptions {
5
+ readonly transport: EmailTransport;
6
+ /** Required only if messages with actions (e.g. approvals) are ever sent — see `render.ts`. */
7
+ readonly actionLinks?: EmailActionLinkOptions;
8
+ }
9
+ /**
10
+ * "Adaptateur email" (L6 task 8), outbound-only — the lot's task list names
11
+ * this adapter without the "commandes entrantes" wording task 4 gave
12
+ * Telegram, so no inbound parsing is built: `capabilities.inbound` is
13
+ * `false`, `verifyIdentity`/`onInbound` are honest refusals rather than a
14
+ * half-built reply-to-link-a-channel flow. `buttons` is `false` too — email
15
+ * has none, hence `render.ts`'s signed-link rendering for actions.
16
+ */
17
+ export declare function createEmailAdapter(options: EmailAdapterOptions): ChannelAdapter;
18
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/email/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,KAAK,sBAAsB,EAAsB,MAAM,aAAa,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAIpD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAA;IAClC,+FAA+F;IAC/F,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,CAAA;CAC9C;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CA8B/E"}
@@ -0,0 +1,41 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { renderEmailMessage } from './render.js';
3
+ const CHANNEL_NAME = 'email';
4
+ /**
5
+ * "Adaptateur email" (L6 task 8), outbound-only — the lot's task list names
6
+ * this adapter without the "commandes entrantes" wording task 4 gave
7
+ * Telegram, so no inbound parsing is built: `capabilities.inbound` is
8
+ * `false`, `verifyIdentity`/`onInbound` are honest refusals rather than a
9
+ * half-built reply-to-link-a-channel flow. `buttons` is `false` too — email
10
+ * has none, hence `render.ts`'s signed-link rendering for actions.
11
+ */
12
+ export function createEmailAdapter(options) {
13
+ return {
14
+ name: CHANNEL_NAME,
15
+ capabilities: {
16
+ richText: true,
17
+ buttons: false,
18
+ threads: false,
19
+ attachments: false,
20
+ inbound: false,
21
+ },
22
+ async send(target, message) {
23
+ const rendered = renderEmailMessage(message, options.actionLinks);
24
+ const sent = await options.transport.send({
25
+ to: target.id,
26
+ subject: rendered.subject,
27
+ text: rendered.text,
28
+ html: rendered.html,
29
+ });
30
+ return sent.messageId;
31
+ },
32
+ async verifyIdentity() {
33
+ throw new CogentaError({
34
+ code: 'CHANNEL_EMAIL_INBOUND_UNSUPPORTED',
35
+ message: 'The email adapter does not support identity linking — it is outbound-only.',
36
+ hint: 'Link accounts through a channel with capabilities.inbound === true (e.g. Telegram).',
37
+ });
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/providers/email/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAA+B,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAG7E,MAAM,YAAY,GAAG,OAAO,CAAA;AAQ5B;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC7D,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,KAAK;YAClB,OAAO,EAAE,KAAK;SACf;QAED,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,OAAuB;YACvD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;YACjE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC;gBACxC,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;aACpB,CAAC,CAAA;YACF,OAAO,IAAI,CAAC,SAAS,CAAA;QACvB,CAAC;QAED,KAAK,CAAC,cAAc;YAClB,MAAM,IAAI,YAAY,CAAC;gBACrB,IAAI,EAAE,mCAAmC;gBACzC,OAAO,EAAE,4EAA4E;gBACrF,IAAI,EAAE,qFAAqF;aAC5F,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { EmailTransport } from './transport.js';
2
+ export interface FileEmailTransportOptions {
3
+ /** Directory an outgoing message is written into — created if missing. */
4
+ readonly directory: string;
5
+ }
6
+ /**
7
+ * The degraded, no-external-service `EmailTransport` (R1) — writes each
8
+ * message as a real file (subject/to headers plus both bodies) rather than
9
+ * sending it anywhere. Useful for local development and for driving real
10
+ * tests without a network, exactly like `StorageDriver`'s local filesystem
11
+ * implementation stands in for S3/R2/MinIO.
12
+ */
13
+ export declare function createFileEmailTransport(options: FileEmailTransportOptions): EmailTransport;
14
+ //# sourceMappingURL=file-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transport.d.ts","sourceRoot":"","sources":["../../../src/providers/email/file-transport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,gBAAgB,CAAA;AAE9E,MAAM,WAAW,yBAAyB;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,cAAc,CAqB3F"}
@@ -0,0 +1,33 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ /**
5
+ * The degraded, no-external-service `EmailTransport` (R1) — writes each
6
+ * message as a real file (subject/to headers plus both bodies) rather than
7
+ * sending it anywhere. Useful for local development and for driving real
8
+ * tests without a network, exactly like `StorageDriver`'s local filesystem
9
+ * implementation stands in for S3/R2/MinIO.
10
+ */
11
+ export function createFileEmailTransport(options) {
12
+ return {
13
+ async send(email) {
14
+ await mkdir(options.directory, { recursive: true });
15
+ const messageId = randomUUID();
16
+ const contents = [
17
+ `To: ${email.to}`,
18
+ `Subject: ${email.subject}`,
19
+ `Message-Id: ${messageId}`,
20
+ '',
21
+ '--- text/plain ---',
22
+ email.text,
23
+ '',
24
+ '--- text/html ---',
25
+ email.html,
26
+ '',
27
+ ].join('\n');
28
+ await writeFile(join(options.directory, `${messageId}.eml.txt`), contents, 'utf8');
29
+ return { messageId };
30
+ },
31
+ };
32
+ }
33
+ //# sourceMappingURL=file-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transport.js","sourceRoot":"","sources":["../../../src/providers/email/file-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAQhC;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAkC;IACzE,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,KAAoB;YAC7B,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACnD,MAAM,SAAS,GAAG,UAAU,EAAE,CAAA;YAC9B,MAAM,QAAQ,GAAG;gBACf,OAAO,KAAK,CAAC,EAAE,EAAE;gBACjB,YAAY,KAAK,CAAC,OAAO,EAAE;gBAC3B,eAAe,SAAS,EAAE;gBAC1B,EAAE;gBACF,oBAAoB;gBACpB,KAAK,CAAC,IAAI;gBACV,EAAE;gBACF,mBAAmB;gBACnB,KAAK,CAAC,IAAI;gBACV,EAAE;aACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACZ,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;YAClF,OAAO,EAAE,SAAS,EAAE,CAAA;QACtB,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { ChannelMessage } from '../../adapter.js';
2
+ export interface EmailActionLinkOptions {
3
+ /** Where a future verification route lives — no such route is built yet (same gap `signed-link.ts` already documents for every buttonless channel). */
4
+ readonly baseUrl: string;
5
+ readonly signingKey: string;
6
+ readonly expiresInSeconds: number;
7
+ }
8
+ export interface RenderedEmailMessage {
9
+ readonly subject: string;
10
+ readonly text: string;
11
+ readonly html: string;
12
+ }
13
+ /**
14
+ * Fresh, email-native rendering — the "on n'écrit pas de Markdown Telegram
15
+ * dans le code métier" boundary (L6, `## Interface`) cuts both ways: nothing
16
+ * from `providers/telegram/render.ts` is reused here, and nothing here leaks
17
+ * into `ChannelMessage` itself.
18
+ */
19
+ export declare function renderEmailMessage(message: ChannelMessage, actionLinks?: EmailActionLinkOptions): RenderedEmailMessage;
20
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/providers/email/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGrE,MAAM,WAAW,sBAAsB;IACrC,uJAAuJ;IACvJ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AA4DD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,cAAc,EACvB,WAAW,CAAC,EAAE,sBAAsB,GACnC,oBAAoB,CA0CtB"}
@@ -0,0 +1,89 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { buildSignedApprovalLink } from '../../approvals/signed-link.js';
3
+ function escapeHtml(value) {
4
+ return value
5
+ .replace(/&/g, '&')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, '&quot;');
9
+ }
10
+ /**
11
+ * `action.id` is command text (`"approve <token>"`/`"deny <token>"`,
12
+ * `approvals/message.ts`) — the same string a button-capable channel routes
13
+ * through `CommandRouter`. Email has no buttons ("## Approbations depuis le
14
+ * canal": "Sur un canal sans boutons [...] l'action est un lien signé à
15
+ * usage unique."), so it turns that same command text into the equivalent
16
+ * signed link instead of inventing a parallel action representation.
17
+ * Approve/deny is the only action shape any real producer emits today
18
+ * (`dispatchApproval`) — anything else is refused rather than guessed at.
19
+ */
20
+ function actionToLink(action, links) {
21
+ const match = /^(approve|deny)\s+(.+)$/.exec(action.id);
22
+ if (match === null) {
23
+ throw new CogentaError({
24
+ code: 'CHANNEL_EMAIL_TRANSPORT_ERROR',
25
+ message: `Email cannot render the action "${action.id}" as a signed link — its id is not in the "approve <token>"/"deny <token>" shape every real producer emits.`,
26
+ hint: 'Only approval actions (dispatchApproval) are rendered by this adapter today.',
27
+ details: { actionId: action.id },
28
+ });
29
+ }
30
+ const [, word = '', token = ''] = match;
31
+ const decision = word === 'approve' ? 'approved' : 'rejected';
32
+ return buildSignedApprovalLink(links.baseUrl, links.signingKey, token, decision, links.expiresInSeconds);
33
+ }
34
+ function renderActions(actions, links) {
35
+ if (actions === undefined || actions.length === 0)
36
+ return { text: '', html: '' };
37
+ if (links === undefined) {
38
+ throw new CogentaError({
39
+ code: 'CHANNEL_EMAIL_TRANSPORT_ERROR',
40
+ message: 'This message carries actions but the email adapter was not configured with actionLinks.',
41
+ hint: 'Pass { actionLinks: { baseUrl, signingKey, expiresInSeconds } } to createEmailAdapter.',
42
+ });
43
+ }
44
+ const rows = actions.map((action) => ({ label: action.label, url: actionToLink(action, links) }));
45
+ const text = `\n\n${rows.map((row) => `${row.label} : ${row.url}`).join('\n')}`;
46
+ const html = `<p>${rows.map((row) => `<a href="${escapeHtml(row.url)}">${escapeHtml(row.label)}</a>`).join(' &nbsp;|&nbsp; ')}</p>`;
47
+ return { text, html };
48
+ }
49
+ /**
50
+ * Fresh, email-native rendering — the "on n'écrit pas de Markdown Telegram
51
+ * dans le code métier" boundary (L6, `## Interface`) cuts both ways: nothing
52
+ * from `providers/telegram/render.ts` is reused here, and nothing here leaks
53
+ * into `ChannelMessage` itself.
54
+ */
55
+ export function renderEmailMessage(message, actionLinks) {
56
+ if (message.level === 'alert') {
57
+ const actions = renderActions(message.actions, actionLinks);
58
+ const severityLabel = message.severity.toUpperCase();
59
+ return {
60
+ subject: `[${severityLabel}] ${message.title}`,
61
+ text: `${message.context}\n\nAction attendue : ${message.expectedAction}\n\nAdmin : ${message.adminUrl}${actions.text}`,
62
+ html: `<p><strong>${escapeHtml(message.context)}</strong></p>` +
63
+ `<p>Action attendue : ${escapeHtml(message.expectedAction)}</p>` +
64
+ `<p><a href="${escapeHtml(message.adminUrl)}">Voir dans l'admin</a></p>${actions.html}`,
65
+ };
66
+ }
67
+ if (message.level === 'report') {
68
+ const figures = message.keyFigures.map((figure) => `${figure.label} : ${figure.value}`);
69
+ const sections = message.sections.map((section) => section.heading === undefined ? section.body : `${section.heading}\n${section.body}`);
70
+ const moreLine = message.moreUrl === undefined ? '' : `\n\nVoir plus : ${message.moreUrl}`;
71
+ return {
72
+ subject: message.title,
73
+ text: `${figures.join(' · ')}\n\n${sections.join('\n\n')}${moreLine}`,
74
+ html: `<p>${figures.map(escapeHtml).join(' &middot; ')}</p>` +
75
+ message.sections
76
+ .map((section) => `<div>${section.heading === undefined ? '' : `<h3>${escapeHtml(section.heading)}</h3>`}<p>${escapeHtml(section.body)}</p></div>`)
77
+ .join('') +
78
+ (message.moreUrl === undefined
79
+ ? ''
80
+ : `<p><a href="${escapeHtml(message.moreUrl)}">Voir plus</a></p>`),
81
+ };
82
+ }
83
+ return {
84
+ subject: message.text,
85
+ text: message.text,
86
+ html: `<p>${escapeHtml(message.text)}</p>`,
87
+ };
88
+ }
89
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/providers/email/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * R1 — "Aucune dépendance dure à une infrastructure... Tout besoin
3
+ * d'infrastructure passe par une interface avec au moins deux
4
+ * implémentations, dont une sans service externe." Real SMTP (protocol
5
+ * state machine, STARTTLS negotiation, MIME) is a materially larger
6
+ * undertaking than Telegram's plain-HTTP Bot API (L6 task 4) — closer to
7
+ * the WXR-XML-parsing precedent (`@cogenta/import`) than to a REST client.
8
+ * This interface exists so `createEmailAdapter` never depends on how mail
9
+ * actually leaves the process; only the local, degraded `FileEmailTransport`
10
+ * is built in this pass — a real SMTP/HTTP-API transport is a documented,
11
+ * deliberate follow-up (see `email` package doc comment), not built here.
12
+ */
13
+ export interface OutgoingEmail {
14
+ readonly to: string;
15
+ readonly subject: string;
16
+ readonly text: string;
17
+ readonly html: string;
18
+ }
19
+ export interface SentEmail {
20
+ readonly messageId: string;
21
+ }
22
+ export interface EmailTransport {
23
+ send(email: OutgoingEmail): Promise<SentEmail>;
24
+ }
25
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/providers/email/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CAC/C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/providers/email/transport.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import type { ChannelAdapter, ChannelMessage, InboundHandler, MessageId } from '../../adapter.js';
2
+ import type { CommandRouter } from '../../inbound/router.js';
3
+ import type { ChannelLinkStore } from '../../linking/store.js';
4
+ import { type SlackClientConfig } from './client.js';
5
+ import { type SlackSocketClient } from './socket.js';
6
+ export interface SlackLinkProof {
7
+ readonly code: string;
8
+ readonly channelUserId: string;
9
+ }
10
+ export interface SlackAdapterOptions extends SlackClientConfig {
11
+ readonly appToken: string;
12
+ readonly linkStore: ChannelLinkStore;
13
+ readonly router: CommandRouter;
14
+ readonly socketClient?: SlackSocketClient;
15
+ }
16
+ /**
17
+ * A live channel adapter beyond the generic interface: `start()`/`stop()`
18
+ * manage the Socket Mode connection, which has no counterpart in
19
+ * `ChannelAdapter` — same shape as `TelegramAdapter`'s polling lifecycle.
20
+ */
21
+ export interface SlackAdapter extends ChannelAdapter {
22
+ update(id: MessageId, message: ChannelMessage): Promise<void>;
23
+ onInbound(handler: InboundHandler): void;
24
+ start(): Promise<void>;
25
+ stop(): void;
26
+ }
27
+ /**
28
+ * "Adaptateur Slack." Socket Mode, not the Events API webhook — same
29
+ * reasoning as Telegram's long-polling choice (`providers/telegram/adapter.ts`):
30
+ * no plane of this project is deployed publicly yet, and Socket Mode is
31
+ * Slack's own real, officially-supported answer to "no public URL,"
32
+ * requiring no signature-verification infrastructure to half-build.
33
+ */
34
+ export declare function createSlackAdapter(options: SlackAdapterOptions): SlackAdapter;
35
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/slack/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EAEd,cAAc,EACd,SAAS,EACV,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAuC,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGzF,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAI7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAWD,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAA;CAC1C;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7D,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,IAAI,CAAA;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAgF7E"}
@@ -0,0 +1,91 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { createSlackClient } from './client.js';
3
+ import { createSlackInboundHandler } from './inbound.js';
4
+ import { renderSlackMessage } from './render.js';
5
+ import { createSlackSocketClient } from './socket.js';
6
+ const CHANNEL_NAME = 'slack';
7
+ function isSlackLinkProof(proof) {
8
+ return (typeof proof === 'object' &&
9
+ proof !== null &&
10
+ typeof proof.code === 'string' &&
11
+ typeof proof.channelUserId === 'string');
12
+ }
13
+ /**
14
+ * "Adaptateur Slack." Socket Mode, not the Events API webhook — same
15
+ * reasoning as Telegram's long-polling choice (`providers/telegram/adapter.ts`):
16
+ * no plane of this project is deployed publicly yet, and Socket Mode is
17
+ * Slack's own real, officially-supported answer to "no public URL,"
18
+ * requiring no signature-verification infrastructure to half-build.
19
+ */
20
+ export function createSlackAdapter(options) {
21
+ const client = createSlackClient(options);
22
+ const socket = options.socketClient ??
23
+ createSlackSocketClient({
24
+ appToken: options.appToken,
25
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
26
+ });
27
+ let inboundHandler;
28
+ const handleEnvelope = createSlackInboundHandler({
29
+ client,
30
+ linkStore: options.linkStore,
31
+ router: options.router,
32
+ getExternalHandler: () => inboundHandler,
33
+ });
34
+ return {
35
+ name: CHANNEL_NAME,
36
+ capabilities: {
37
+ richText: true,
38
+ buttons: true,
39
+ threads: false,
40
+ attachments: false,
41
+ inbound: true,
42
+ },
43
+ async send(target, message) {
44
+ const rendered = renderSlackMessage(message);
45
+ const sent = await client.postMessage({
46
+ channel: target.id,
47
+ text: rendered.text,
48
+ ...(rendered.blocks.length > 0 ? { blocks: rendered.blocks } : {}),
49
+ });
50
+ return `${sent.channel}:${sent.ts}`;
51
+ },
52
+ async update(id, message) {
53
+ const [channel, ts] = id.split(':');
54
+ if (channel === undefined || ts === undefined) {
55
+ throw new CogentaError({
56
+ code: 'CHANNEL_SLACK_API_ERROR',
57
+ message: `"${id}" is not a message id this adapter produced.`,
58
+ hint: "Only pass a MessageId returned by this adapter's own send() to update().",
59
+ });
60
+ }
61
+ const rendered = renderSlackMessage(message);
62
+ await client.updateMessage({
63
+ channel,
64
+ ts,
65
+ text: rendered.text,
66
+ ...(rendered.blocks.length > 0 ? { blocks: rendered.blocks } : {}),
67
+ });
68
+ },
69
+ onInbound(handler) {
70
+ inboundHandler = handler;
71
+ },
72
+ async verifyIdentity(proof) {
73
+ if (!isSlackLinkProof(proof)) {
74
+ throw new CogentaError({
75
+ code: 'CHANNEL_SLACK_API_ERROR',
76
+ message: 'Slack identity proof must be { code, channelUserId }.',
77
+ hint: 'Pass the linking code submitted in the channel and the Slack user id it came from.',
78
+ });
79
+ }
80
+ await options.linkStore.verifyCode(proof.code, CHANNEL_NAME, proof.channelUserId);
81
+ return options.linkStore.resolveIdentity(CHANNEL_NAME, proof.channelUserId);
82
+ },
83
+ async start() {
84
+ await socket.connect(handleEnvelope);
85
+ },
86
+ stop() {
87
+ socket.disconnect();
88
+ },
89
+ };
90
+ }
91
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/providers/slack/adapter.ts"],"names":[],"mappings":"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"}