@cat-factory/integrations 0.6.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 (242) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +68 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +84 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/modules/datadog/DatadogClient.d.ts +54 -0
  7. package/dist/modules/datadog/DatadogClient.d.ts.map +1 -0
  8. package/dist/modules/datadog/DatadogClient.js +132 -0
  9. package/dist/modules/datadog/DatadogClient.js.map +1 -0
  10. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts +30 -0
  11. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts.map +1 -0
  12. package/dist/modules/datadog/DatadogReleaseHealthProvider.js +101 -0
  13. package/dist/modules/datadog/DatadogReleaseHealthProvider.js.map +1 -0
  14. package/dist/modules/datadog/datadog.logic.d.ts +37 -0
  15. package/dist/modules/datadog/datadog.logic.d.ts.map +1 -0
  16. package/dist/modules/datadog/datadog.logic.js +90 -0
  17. package/dist/modules/datadog/datadog.logic.js.map +1 -0
  18. package/dist/modules/documents/ConfluenceProvider.d.ts +29 -0
  19. package/dist/modules/documents/ConfluenceProvider.d.ts.map +1 -0
  20. package/dist/modules/documents/ConfluenceProvider.js +180 -0
  21. package/dist/modules/documents/ConfluenceProvider.js.map +1 -0
  22. package/dist/modules/documents/DocumentConnectionService.d.ts +30 -0
  23. package/dist/modules/documents/DocumentConnectionService.d.ts.map +1 -0
  24. package/dist/modules/documents/DocumentConnectionService.js +69 -0
  25. package/dist/modules/documents/DocumentConnectionService.js.map +1 -0
  26. package/dist/modules/documents/DocumentImportService.d.ts +34 -0
  27. package/dist/modules/documents/DocumentImportService.d.ts.map +1 -0
  28. package/dist/modules/documents/DocumentImportService.js +83 -0
  29. package/dist/modules/documents/DocumentImportService.js.map +1 -0
  30. package/dist/modules/documents/DocumentLinkService.d.ts +31 -0
  31. package/dist/modules/documents/DocumentLinkService.d.ts.map +1 -0
  32. package/dist/modules/documents/DocumentLinkService.js +75 -0
  33. package/dist/modules/documents/DocumentLinkService.js.map +1 -0
  34. package/dist/modules/documents/DocumentPlannerService.d.ts +23 -0
  35. package/dist/modules/documents/DocumentPlannerService.d.ts.map +1 -0
  36. package/dist/modules/documents/DocumentPlannerService.js +96 -0
  37. package/dist/modules/documents/DocumentPlannerService.js.map +1 -0
  38. package/dist/modules/documents/GitHubDocsProvider.d.ts +42 -0
  39. package/dist/modules/documents/GitHubDocsProvider.d.ts.map +1 -0
  40. package/dist/modules/documents/GitHubDocsProvider.js +86 -0
  41. package/dist/modules/documents/GitHubDocsProvider.js.map +1 -0
  42. package/dist/modules/documents/NotionProvider.d.ts +32 -0
  43. package/dist/modules/documents/NotionProvider.d.ts.map +1 -0
  44. package/dist/modules/documents/NotionProvider.js +221 -0
  45. package/dist/modules/documents/NotionProvider.js.map +1 -0
  46. package/dist/modules/documents/confluence.logic.d.ts +37 -0
  47. package/dist/modules/documents/confluence.logic.d.ts.map +1 -0
  48. package/dist/modules/documents/confluence.logic.js +133 -0
  49. package/dist/modules/documents/confluence.logic.js.map +1 -0
  50. package/dist/modules/documents/documents.logic.d.ts +22 -0
  51. package/dist/modules/documents/documents.logic.d.ts.map +1 -0
  52. package/dist/modules/documents/documents.logic.js +138 -0
  53. package/dist/modules/documents/documents.logic.js.map +1 -0
  54. package/dist/modules/documents/github-docs.logic.d.ts +52 -0
  55. package/dist/modules/documents/github-docs.logic.d.ts.map +1 -0
  56. package/dist/modules/documents/github-docs.logic.js +94 -0
  57. package/dist/modules/documents/github-docs.logic.js.map +1 -0
  58. package/dist/modules/documents/notion.logic.d.ts +31 -0
  59. package/dist/modules/documents/notion.logic.d.ts.map +1 -0
  60. package/dist/modules/documents/notion.logic.js +142 -0
  61. package/dist/modules/documents/notion.logic.js.map +1 -0
  62. package/dist/modules/email/EmailConnectionService.d.ts +34 -0
  63. package/dist/modules/email/EmailConnectionService.d.ts.map +1 -0
  64. package/dist/modules/email/EmailConnectionService.js +82 -0
  65. package/dist/modules/email/EmailConnectionService.js.map +1 -0
  66. package/dist/modules/email/adapters.d.ts +39 -0
  67. package/dist/modules/email/adapters.d.ts.map +1 -0
  68. package/dist/modules/email/adapters.js +79 -0
  69. package/dist/modules/email/adapters.js.map +1 -0
  70. package/dist/modules/environments/EnvironmentConnectionService.d.ts +42 -0
  71. package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -0
  72. package/dist/modules/environments/EnvironmentConnectionService.js +120 -0
  73. package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -0
  74. package/dist/modules/environments/EnvironmentProvisioningService.d.ts +56 -0
  75. package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -0
  76. package/dist/modules/environments/EnvironmentProvisioningService.js +153 -0
  77. package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -0
  78. package/dist/modules/environments/EnvironmentTeardownService.d.ts +24 -0
  79. package/dist/modules/environments/EnvironmentTeardownService.d.ts.map +1 -0
  80. package/dist/modules/environments/EnvironmentTeardownService.js +54 -0
  81. package/dist/modules/environments/EnvironmentTeardownService.js.map +1 -0
  82. package/dist/modules/environments/HttpEnvironmentProvider.d.ts +30 -0
  83. package/dist/modules/environments/HttpEnvironmentProvider.d.ts.map +1 -0
  84. package/dist/modules/environments/HttpEnvironmentProvider.js +316 -0
  85. package/dist/modules/environments/HttpEnvironmentProvider.js.map +1 -0
  86. package/dist/modules/environments/environments.logic.d.ts +50 -0
  87. package/dist/modules/environments/environments.logic.d.ts.map +1 -0
  88. package/dist/modules/environments/environments.logic.js +257 -0
  89. package/dist/modules/environments/environments.logic.js.map +1 -0
  90. package/dist/modules/github/GitHubInstallationService.d.ts +66 -0
  91. package/dist/modules/github/GitHubInstallationService.d.ts.map +1 -0
  92. package/dist/modules/github/GitHubInstallationService.js +143 -0
  93. package/dist/modules/github/GitHubInstallationService.js.map +1 -0
  94. package/dist/modules/github/GitHubService.d.ts +29 -0
  95. package/dist/modules/github/GitHubService.d.ts.map +1 -0
  96. package/dist/modules/github/GitHubService.js +61 -0
  97. package/dist/modules/github/GitHubService.js.map +1 -0
  98. package/dist/modules/github/GitHubSyncService.d.ts +97 -0
  99. package/dist/modules/github/GitHubSyncService.d.ts.map +1 -0
  100. package/dist/modules/github/GitHubSyncService.js +241 -0
  101. package/dist/modules/github/GitHubSyncService.js.map +1 -0
  102. package/dist/modules/github/RepoProvisioningService.d.ts +26 -0
  103. package/dist/modules/github/RepoProvisioningService.d.ts.map +1 -0
  104. package/dist/modules/github/RepoProvisioningService.js +36 -0
  105. package/dist/modules/github/RepoProvisioningService.js.map +1 -0
  106. package/dist/modules/github/WebhookService.d.ts +28 -0
  107. package/dist/modules/github/WebhookService.d.ts.map +1 -0
  108. package/dist/modules/github/WebhookService.js +156 -0
  109. package/dist/modules/github/WebhookService.js.map +1 -0
  110. package/dist/modules/github/projection.logic.d.ts +95 -0
  111. package/dist/modules/github/projection.logic.d.ts.map +1 -0
  112. package/dist/modules/github/projection.logic.js +94 -0
  113. package/dist/modules/github/projection.logic.js.map +1 -0
  114. package/dist/modules/github/provisioning.logic.d.ts +11 -0
  115. package/dist/modules/github/provisioning.logic.d.ts.map +1 -0
  116. package/dist/modules/github/provisioning.logic.js +18 -0
  117. package/dist/modules/github/provisioning.logic.js.map +1 -0
  118. package/dist/modules/incident/incident.logic.d.ts +16 -0
  119. package/dist/modules/incident/incident.logic.d.ts.map +1 -0
  120. package/dist/modules/incident/incident.logic.js +23 -0
  121. package/dist/modules/incident/incident.logic.js.map +1 -0
  122. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts +26 -0
  123. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts.map +1 -0
  124. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js +84 -0
  125. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js.map +1 -0
  126. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts +27 -0
  127. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts.map +1 -0
  128. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js +65 -0
  129. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js.map +1 -0
  130. package/dist/modules/providers/ApiKeyService.d.ts +73 -0
  131. package/dist/modules/providers/ApiKeyService.d.ts.map +1 -0
  132. package/dist/modules/providers/ApiKeyService.js +122 -0
  133. package/dist/modules/providers/ApiKeyService.js.map +1 -0
  134. package/dist/modules/providers/LocalModelEndpointService.d.ts +52 -0
  135. package/dist/modules/providers/LocalModelEndpointService.d.ts.map +1 -0
  136. package/dist/modules/providers/LocalModelEndpointService.js +131 -0
  137. package/dist/modules/providers/LocalModelEndpointService.js.map +1 -0
  138. package/dist/modules/providers/PersonalSubscriptionService.d.ts +94 -0
  139. package/dist/modules/providers/PersonalSubscriptionService.d.ts.map +1 -0
  140. package/dist/modules/providers/PersonalSubscriptionService.js +218 -0
  141. package/dist/modules/providers/PersonalSubscriptionService.js.map +1 -0
  142. package/dist/modules/providers/ProviderSubscriptionService.d.ts +75 -0
  143. package/dist/modules/providers/ProviderSubscriptionService.d.ts.map +1 -0
  144. package/dist/modules/providers/ProviderSubscriptionService.js +130 -0
  145. package/dist/modules/providers/ProviderSubscriptionService.js.map +1 -0
  146. package/dist/modules/providers/localModelUrl.d.ts +7 -0
  147. package/dist/modules/providers/localModelUrl.d.ts.map +1 -0
  148. package/dist/modules/providers/localModelUrl.js +67 -0
  149. package/dist/modules/providers/localModelUrl.js.map +1 -0
  150. package/dist/modules/providers/providers.logic.d.ts +23 -0
  151. package/dist/modules/providers/providers.logic.d.ts.map +1 -0
  152. package/dist/modules/providers/providers.logic.js +46 -0
  153. package/dist/modules/providers/providers.logic.js.map +1 -0
  154. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts +51 -0
  155. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts.map +1 -0
  156. package/dist/modules/runners/HttpRunnerPoolProvider.js +304 -0
  157. package/dist/modules/runners/HttpRunnerPoolProvider.js.map +1 -0
  158. package/dist/modules/runners/RunnerPoolConnectionService.d.ts +47 -0
  159. package/dist/modules/runners/RunnerPoolConnectionService.d.ts.map +1 -0
  160. package/dist/modules/runners/RunnerPoolConnectionService.js +98 -0
  161. package/dist/modules/runners/RunnerPoolConnectionService.js.map +1 -0
  162. package/dist/modules/runners/RunnerPoolTransport.d.ts +11 -0
  163. package/dist/modules/runners/RunnerPoolTransport.d.ts.map +1 -0
  164. package/dist/modules/runners/RunnerPoolTransport.js +61 -0
  165. package/dist/modules/runners/RunnerPoolTransport.js.map +1 -0
  166. package/dist/modules/runners/runners.logic.d.ts +16 -0
  167. package/dist/modules/runners/runners.logic.d.ts.map +1 -0
  168. package/dist/modules/runners/runners.logic.js +52 -0
  169. package/dist/modules/runners/runners.logic.js.map +1 -0
  170. package/dist/modules/slack/SlackApiClient.d.ts +67 -0
  171. package/dist/modules/slack/SlackApiClient.d.ts.map +1 -0
  172. package/dist/modules/slack/SlackApiClient.js +132 -0
  173. package/dist/modules/slack/SlackApiClient.js.map +1 -0
  174. package/dist/modules/slack/SlackConnectionService.d.ts +41 -0
  175. package/dist/modules/slack/SlackConnectionService.d.ts.map +1 -0
  176. package/dist/modules/slack/SlackConnectionService.js +136 -0
  177. package/dist/modules/slack/SlackConnectionService.js.map +1 -0
  178. package/dist/modules/slack/SlackMemberMappingService.d.ts +17 -0
  179. package/dist/modules/slack/SlackMemberMappingService.d.ts.map +1 -0
  180. package/dist/modules/slack/SlackMemberMappingService.js +28 -0
  181. package/dist/modules/slack/SlackMemberMappingService.js.map +1 -0
  182. package/dist/modules/slack/SlackNotificationChannel.d.ts +45 -0
  183. package/dist/modules/slack/SlackNotificationChannel.d.ts.map +1 -0
  184. package/dist/modules/slack/SlackNotificationChannel.js +84 -0
  185. package/dist/modules/slack/SlackNotificationChannel.js.map +1 -0
  186. package/dist/modules/slack/SlackSettingsService.d.ts +16 -0
  187. package/dist/modules/slack/SlackSettingsService.d.ts.map +1 -0
  188. package/dist/modules/slack/SlackSettingsService.js +41 -0
  189. package/dist/modules/slack/SlackSettingsService.js.map +1 -0
  190. package/dist/modules/slack/slack.logic.d.ts +55 -0
  191. package/dist/modules/slack/slack.logic.d.ts.map +1 -0
  192. package/dist/modules/slack/slack.logic.js +149 -0
  193. package/dist/modules/slack/slack.logic.js.map +1 -0
  194. package/dist/modules/tasks/GitHubIssuesProvider.d.ts +50 -0
  195. package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -0
  196. package/dist/modules/tasks/GitHubIssuesProvider.js +92 -0
  197. package/dist/modules/tasks/GitHubIssuesProvider.js.map +1 -0
  198. package/dist/modules/tasks/JiraProvider.d.ts +29 -0
  199. package/dist/modules/tasks/JiraProvider.d.ts.map +1 -0
  200. package/dist/modules/tasks/JiraProvider.js +114 -0
  201. package/dist/modules/tasks/JiraProvider.js.map +1 -0
  202. package/dist/modules/tasks/TaskConnectionService.d.ts +30 -0
  203. package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -0
  204. package/dist/modules/tasks/TaskConnectionService.js +69 -0
  205. package/dist/modules/tasks/TaskConnectionService.js.map +1 -0
  206. package/dist/modules/tasks/TaskImportService.d.ts +34 -0
  207. package/dist/modules/tasks/TaskImportService.d.ts.map +1 -0
  208. package/dist/modules/tasks/TaskImportService.js +96 -0
  209. package/dist/modules/tasks/TaskImportService.js.map +1 -0
  210. package/dist/modules/tasks/TaskLinkService.d.ts +30 -0
  211. package/dist/modules/tasks/TaskLinkService.d.ts.map +1 -0
  212. package/dist/modules/tasks/TaskLinkService.js +56 -0
  213. package/dist/modules/tasks/TaskLinkService.js.map +1 -0
  214. package/dist/modules/tasks/github-issues.logic.d.ts +35 -0
  215. package/dist/modules/tasks/github-issues.logic.d.ts.map +1 -0
  216. package/dist/modules/tasks/github-issues.logic.js +67 -0
  217. package/dist/modules/tasks/github-issues.logic.js.map +1 -0
  218. package/dist/modules/tasks/jira.logic.d.ts +28 -0
  219. package/dist/modules/tasks/jira.logic.d.ts.map +1 -0
  220. package/dist/modules/tasks/jira.logic.js +151 -0
  221. package/dist/modules/tasks/jira.logic.js.map +1 -0
  222. package/dist/modules/tasks/tasks.logic.d.ts +12 -0
  223. package/dist/modules/tasks/tasks.logic.d.ts.map +1 -0
  224. package/dist/modules/tasks/tasks.logic.js +17 -0
  225. package/dist/modules/tasks/tasks.logic.js.map +1 -0
  226. package/dist/modules/tracker/TicketTrackerService.d.ts +45 -0
  227. package/dist/modules/tracker/TicketTrackerService.d.ts.map +1 -0
  228. package/dist/modules/tracker/TicketTrackerService.js +52 -0
  229. package/dist/modules/tracker/TicketTrackerService.js.map +1 -0
  230. package/dist/modules/tracker/base64.d.ts +2 -0
  231. package/dist/modules/tracker/base64.d.ts.map +1 -0
  232. package/dist/modules/tracker/base64.js +18 -0
  233. package/dist/modules/tracker/base64.js.map +1 -0
  234. package/dist/modules/tracker/github.create.logic.d.ts +16 -0
  235. package/dist/modules/tracker/github.create.logic.d.ts.map +1 -0
  236. package/dist/modules/tracker/github.create.logic.js +25 -0
  237. package/dist/modules/tracker/github.create.logic.js.map +1 -0
  238. package/dist/modules/tracker/jira.create.logic.d.ts +31 -0
  239. package/dist/modules/tracker/jira.create.logic.d.ts.map +1 -0
  240. package/dist/modules/tracker/jira.create.logic.js +59 -0
  241. package/dist/modules/tracker/jira.create.logic.js.map +1 -0
  242. package/package.json +36 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runners.logic.js","sourceRoot":"","sources":["../../../src/modules/runners/runners.logic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAA;AAEhF,4EAA4E;AAC5E,0EAA0E;AAC1E,+EAA+E;AAC/E,kFAAkF;AAClF,6EAA6E;AAC7E,kEAAkE;AAElE,oEAAoE;AACpE,MAAM,UAAU,oBAAoB,CAAC,QAA4B;IAC/D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAA;IAC1B,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM;YACT,OAAO,EAAE,CAAA;QACX,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QAC7B,KAAK,OAAO;YACV,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAA;QACjE,KAAK,2BAA2B;YAC9B,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAA;QACrE,KAAK,gBAAgB;YACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IACnD,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,sBAAsB,CACpC,QAA4B,EAC5B,MAAM,GAAoB,wBAAwB;IAElD,wBAAwB,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;IAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,2BAA2B,EAAE,CAAC;QACvD,wBAAwB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAA;IAC7E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,GAAuB,EACvB,SAA6D;IAE7D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;YAC7E,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC,EAAE,CAAA;QACxB,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAA;IACjF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,67 @@
1
+ import type { SlackChannel } from '@cat-factory/kernel';
2
+ /** Result of `auth.test` — the installed team + the bot user the token posts as. */
3
+ export interface SlackAuthInfo {
4
+ teamId: string;
5
+ teamName: string;
6
+ botUserId: string | null;
7
+ url: string | null;
8
+ /** OAuth scopes granted to the token, read from the `x-oauth-scopes` header. */
9
+ scopes: string[];
10
+ }
11
+ /** Result of an `oauth.v2.access` exchange. */
12
+ export interface SlackOAuthResult {
13
+ accessToken: string;
14
+ teamId: string;
15
+ teamName: string;
16
+ botUserId: string | null;
17
+ scopes: string[];
18
+ }
19
+ export declare class SlackApiError extends Error {
20
+ readonly method: string;
21
+ readonly slackError: string;
22
+ constructor(method: string, slackError: string);
23
+ }
24
+ type FetchLike = typeof fetch;
25
+ export interface SlackApiClientOptions {
26
+ /** Override fetch (tests); defaults to the global. */
27
+ fetchImpl?: FetchLike;
28
+ /** Slack API base; defaults to the public endpoint. */
29
+ apiBase?: string;
30
+ }
31
+ export declare class SlackApiClient {
32
+ private readonly fetchImpl;
33
+ private readonly apiBase;
34
+ constructor(options?: SlackApiClientOptions);
35
+ /**
36
+ * Validate a bot token and read the team it belongs to (`auth.test`). The granted
37
+ * OAuth scopes aren't in the body — Slack returns them in the `x-oauth-scopes`
38
+ * response header on any Web API call — so we read them here too, which lets the
39
+ * manual-token onboarding path capture real scopes (not an empty list).
40
+ */
41
+ authTest(token: string): Promise<SlackAuthInfo>;
42
+ /**
43
+ * Exchange an OAuth authorization `code` for a bot token (`oauth.v2.access`).
44
+ * Posted as form-encoded with the app's client credentials.
45
+ */
46
+ oauthAccess(input: {
47
+ clientId: string;
48
+ clientSecret: string;
49
+ code: string;
50
+ redirectUri: string;
51
+ }): Promise<SlackOAuthResult>;
52
+ /** Post a message (`chat.postMessage`). `body` is a rendered message payload. */
53
+ chatPostMessage(token: string, body: Record<string, unknown>): Promise<void>;
54
+ /**
55
+ * List the channels the bot can see (`conversations.list`), for the routing
56
+ * picker. Public + private, excluding archived. Follows the cursor so a workspace
57
+ * with more than one page of channels (>1000) isn't silently truncated, with a
58
+ * page cap as a safety bound against an unbounded loop.
59
+ */
60
+ conversationsList(token: string): Promise<SlackChannel[]>;
61
+ /** POST a JSON body to a Slack method with a bearer token (raw response). */
62
+ private request;
63
+ /** POST a JSON body to a Slack method with a bearer token; throw on `ok:false`. */
64
+ private postJson;
65
+ }
66
+ export {};
67
+ //# sourceMappingURL=SlackApiClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackApiClient.d.ts","sourceRoot":"","sources":["../../../src/modules/slack/SlackApiClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAOvD,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,gFAAgF;IAChF,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM;IAF7B,YACW,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAI5B;CACF;AAED,KAAK,SAAS,GAAG,OAAO,KAAK,CAAA;AAE7B,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAEhC,YAAY,OAAO,GAAE,qBAA0B,EAG9C;IAED;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAWpD;IAED;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAA;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAA;KACpB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsB5B;IAED,iFAAiF;IAC3E,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjF;IAED;;;;;OAKG;IACG,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA0B9D;IAED,6EAA6E;IAC7E,OAAO,CAAC,OAAO;IAWf,mFAAmF;YACrE,QAAQ;CAUvB"}
@@ -0,0 +1,132 @@
1
+ export class SlackApiError extends Error {
2
+ method;
3
+ slackError;
4
+ constructor(method, slackError) {
5
+ super(`Slack ${method} failed: ${slackError}`);
6
+ this.method = method;
7
+ this.slackError = slackError;
8
+ this.name = 'SlackApiError';
9
+ }
10
+ }
11
+ export class SlackApiClient {
12
+ fetchImpl;
13
+ apiBase;
14
+ constructor(options = {}) {
15
+ this.fetchImpl = options.fetchImpl ?? fetch;
16
+ this.apiBase = (options.apiBase ?? 'https://slack.com/api').replace(/\/+$/, '');
17
+ }
18
+ /**
19
+ * Validate a bot token and read the team it belongs to (`auth.test`). The granted
20
+ * OAuth scopes aren't in the body — Slack returns them in the `x-oauth-scopes`
21
+ * response header on any Web API call — so we read them here too, which lets the
22
+ * manual-token onboarding path capture real scopes (not an empty list).
23
+ */
24
+ async authTest(token) {
25
+ const res = await this.request('auth.test', token, {});
26
+ const data = (await res.json());
27
+ if (!data.ok)
28
+ throw new SlackApiError('auth.test', data.error ?? 'unknown_error');
29
+ return {
30
+ teamId: String(data.team_id ?? ''),
31
+ teamName: String(data.team ?? ''),
32
+ botUserId: data.user_id ? String(data.user_id) : null,
33
+ url: data.url ? String(data.url) : null,
34
+ scopes: parseScopeHeader(res.headers.get('x-oauth-scopes')),
35
+ };
36
+ }
37
+ /**
38
+ * Exchange an OAuth authorization `code` for a bot token (`oauth.v2.access`).
39
+ * Posted as form-encoded with the app's client credentials.
40
+ */
41
+ async oauthAccess(input) {
42
+ const form = new URLSearchParams({
43
+ client_id: input.clientId,
44
+ client_secret: input.clientSecret,
45
+ code: input.code,
46
+ redirect_uri: input.redirectUri,
47
+ });
48
+ const res = await this.fetchImpl(`${this.apiBase}/oauth.v2.access`, {
49
+ method: 'POST',
50
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
51
+ body: form.toString(),
52
+ });
53
+ const data = (await res.json());
54
+ if (!data.ok)
55
+ throw new SlackApiError('oauth.v2.access', data.error ?? 'unknown_error');
56
+ const team = (data.team ?? {});
57
+ return {
58
+ accessToken: String(data.access_token ?? ''),
59
+ teamId: String(team.id ?? ''),
60
+ teamName: String(team.name ?? ''),
61
+ botUserId: data.bot_user_id ? String(data.bot_user_id) : null,
62
+ scopes: typeof data.scope === 'string' && data.scope ? data.scope.split(',') : [],
63
+ };
64
+ }
65
+ /** Post a message (`chat.postMessage`). `body` is a rendered message payload. */
66
+ async chatPostMessage(token, body) {
67
+ await this.postJson('chat.postMessage', token, body);
68
+ }
69
+ /**
70
+ * List the channels the bot can see (`conversations.list`), for the routing
71
+ * picker. Public + private, excluding archived. Follows the cursor so a workspace
72
+ * with more than one page of channels (>1000) isn't silently truncated, with a
73
+ * page cap as a safety bound against an unbounded loop.
74
+ */
75
+ async conversationsList(token) {
76
+ const result = [];
77
+ let cursor;
78
+ const MAX_PAGES = 20;
79
+ for (let page = 0; page < MAX_PAGES; page++) {
80
+ const data = await this.postJson('conversations.list', token, {
81
+ types: 'public_channel,private_channel',
82
+ exclude_archived: true,
83
+ limit: 1000,
84
+ ...(cursor ? { cursor } : {}),
85
+ });
86
+ const channels = Array.isArray(data.channels) ? data.channels : [];
87
+ for (const c of channels) {
88
+ const channel = c;
89
+ result.push({
90
+ id: String(channel.id ?? ''),
91
+ name: String(channel.name ?? ''),
92
+ isPrivate: Boolean(channel.is_private),
93
+ });
94
+ }
95
+ const meta = data.response_metadata;
96
+ const next = meta?.next_cursor;
97
+ if (!next)
98
+ break;
99
+ cursor = next;
100
+ }
101
+ return result;
102
+ }
103
+ /** POST a JSON body to a Slack method with a bearer token (raw response). */
104
+ request(method, token, body) {
105
+ return this.fetchImpl(`${this.apiBase}/${method}`, {
106
+ method: 'POST',
107
+ headers: {
108
+ authorization: `Bearer ${token}`,
109
+ 'content-type': 'application/json; charset=utf-8',
110
+ },
111
+ body: JSON.stringify(body),
112
+ });
113
+ }
114
+ /** POST a JSON body to a Slack method with a bearer token; throw on `ok:false`. */
115
+ async postJson(method, token, body) {
116
+ const res = await this.request(method, token, body);
117
+ const data = (await res.json());
118
+ if (!data.ok)
119
+ throw new SlackApiError(method, data.error ?? 'unknown_error');
120
+ return data;
121
+ }
122
+ }
123
+ /** Parse Slack's comma-separated `x-oauth-scopes` header into a trimmed list. */
124
+ function parseScopeHeader(header) {
125
+ if (!header)
126
+ return [];
127
+ return header
128
+ .split(',')
129
+ .map((s) => s.trim())
130
+ .filter((s) => s.length > 0);
131
+ }
132
+ //# sourceMappingURL=SlackApiClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackApiClient.js","sourceRoot":"","sources":["../../../src/modules/slack/SlackApiClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Clock, SecretCipher, WorkspaceRepository } from '@cat-factory/kernel';
2
+ import type { SlackChannel, SlackConnection, SlackConnectionRepository } from '@cat-factory/kernel';
3
+ import { SlackApiClient } from './SlackApiClient.js';
4
+ export interface SlackConnectionServiceDependencies {
5
+ slackConnectionRepository: SlackConnectionRepository;
6
+ workspaceRepository: WorkspaceRepository;
7
+ secretCipher: SecretCipher;
8
+ clock: Clock;
9
+ /** Slack Web API client; defaults to a fetch-backed one. */
10
+ slackClient?: SlackApiClient;
11
+ /** OAuth app credentials, when the deployment registered a Slack app. */
12
+ oauth?: {
13
+ clientId: string;
14
+ clientSecret: string;
15
+ redirectUrl: string;
16
+ };
17
+ }
18
+ export declare class SlackConnectionService {
19
+ private readonly deps;
20
+ private readonly slack;
21
+ constructor(deps: SlackConnectionServiceDependencies);
22
+ /** Whether OAuth onboarding is available (the app credentials were configured). */
23
+ get oauthEnabled(): boolean;
24
+ /** Build the "Add to Slack" authorize URL, embedding a signed `state`. */
25
+ buildInstallUrl(state: string, scopes: string[]): string;
26
+ /** Connect by pasting a bot token (the always-available path). */
27
+ connectWithToken(workspaceId: string, token: string): Promise<SlackConnection>;
28
+ /** Connect by exchanging an OAuth authorization code for a bot token. */
29
+ connectViaOAuth(workspaceId: string, code: string): Promise<SlackConnection>;
30
+ /** The account's current connection (safe metadata), or null. */
31
+ getConnection(workspaceId: string): Promise<SlackConnection | null>;
32
+ /** Disconnect the account's Slack (tombstones the binding). */
33
+ disconnect(workspaceId: string): Promise<void>;
34
+ /** List channels the bot can post to, for the routing picker; [] when not connected. */
35
+ listChannels(workspaceId: string): Promise<SlackChannel[]>;
36
+ /** Encrypt the token + persist the connection, guarding against a cross-account claim. */
37
+ private store;
38
+ /** The per-account scope key for a workspace (account id, else the workspace id). */
39
+ private resolveAccountKey;
40
+ }
41
+ //# sourceMappingURL=SlackConnectionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/slack/SlackConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACnF,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAEf,yBAAyB,EAC1B,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAapD,MAAM,WAAW,kCAAkC;IACjD,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,4DAA4D;IAC5D,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,yEAAyE;IACzE,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;AAsBD,qBAAa,sBAAsB;IAGrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,YAA6B,IAAI,EAAE,kCAAkC,EAEpE;IAED,mFAAmF;IACnF,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,0EAA0E;IAC1E,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CASvD;IAED,kEAAkE;IAC5D,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAWnF;IAED,yEAAyE;IACnE,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBjF;IAED,iEAAiE;IAC3D,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKxE;IAED,+DAA+D;IACzD,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnD;IAED,wFAAwF;IAClF,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAM/D;IAED,0FAA0F;YAC5E,KAAK;IAmCnB,qFAAqF;YACvE,iBAAiB;CAIhC"}
@@ -0,0 +1,136 @@
1
+ import { ConflictError, requireWorkspace } from '@cat-factory/kernel';
2
+ import { SlackApiClient } from './SlackApiClient.js';
3
+ function toConnection(record) {
4
+ let scopes = [];
5
+ if (record.scopesJson) {
6
+ try {
7
+ const parsed = JSON.parse(record.scopesJson);
8
+ if (Array.isArray(parsed))
9
+ scopes = parsed;
10
+ }
11
+ catch {
12
+ scopes = [];
13
+ }
14
+ }
15
+ return {
16
+ teamId: record.teamId,
17
+ teamName: record.teamName,
18
+ teamIconUrl: record.teamIconUrl,
19
+ botUserId: record.botUserId,
20
+ scopes,
21
+ connectedAt: record.createdAt,
22
+ };
23
+ }
24
+ export class SlackConnectionService {
25
+ deps;
26
+ slack;
27
+ constructor(deps) {
28
+ this.deps = deps;
29
+ this.slack = deps.slackClient ?? new SlackApiClient();
30
+ }
31
+ /** Whether OAuth onboarding is available (the app credentials were configured). */
32
+ get oauthEnabled() {
33
+ return Boolean(this.deps.oauth);
34
+ }
35
+ /** Build the "Add to Slack" authorize URL, embedding a signed `state`. */
36
+ buildInstallUrl(state, scopes) {
37
+ if (!this.deps.oauth)
38
+ throw new ConflictError('Slack OAuth is not configured');
39
+ const params = new URLSearchParams({
40
+ client_id: this.deps.oauth.clientId,
41
+ scope: scopes.join(','),
42
+ redirect_uri: this.deps.oauth.redirectUrl,
43
+ state,
44
+ });
45
+ return `https://slack.com/oauth/v2/authorize?${params.toString()}`;
46
+ }
47
+ /** Connect by pasting a bot token (the always-available path). */
48
+ async connectWithToken(workspaceId, token) {
49
+ const accountKey = await this.resolveAccountKey(workspaceId);
50
+ const info = await this.slack.authTest(token);
51
+ return this.store(accountKey, {
52
+ teamId: info.teamId,
53
+ teamName: info.teamName,
54
+ teamIconUrl: null,
55
+ botUserId: info.botUserId,
56
+ scopes: info.scopes,
57
+ token,
58
+ });
59
+ }
60
+ /** Connect by exchanging an OAuth authorization code for a bot token. */
61
+ async connectViaOAuth(workspaceId, code) {
62
+ if (!this.deps.oauth)
63
+ throw new ConflictError('Slack OAuth is not configured');
64
+ const accountKey = await this.resolveAccountKey(workspaceId);
65
+ const result = await this.slack.oauthAccess({
66
+ clientId: this.deps.oauth.clientId,
67
+ clientSecret: this.deps.oauth.clientSecret,
68
+ code,
69
+ redirectUri: this.deps.oauth.redirectUrl,
70
+ });
71
+ return this.store(accountKey, {
72
+ teamId: result.teamId,
73
+ teamName: result.teamName,
74
+ teamIconUrl: null,
75
+ botUserId: result.botUserId,
76
+ scopes: result.scopes,
77
+ token: result.accessToken,
78
+ });
79
+ }
80
+ /** The account's current connection (safe metadata), or null. */
81
+ async getConnection(workspaceId) {
82
+ const accountKey = await this.resolveAccountKey(workspaceId);
83
+ const record = await this.deps.slackConnectionRepository.getByAccount(accountKey);
84
+ if (!record || record.deletedAt)
85
+ return null;
86
+ return toConnection(record);
87
+ }
88
+ /** Disconnect the account's Slack (tombstones the binding). */
89
+ async disconnect(workspaceId) {
90
+ const accountKey = await this.resolveAccountKey(workspaceId);
91
+ const record = await this.deps.slackConnectionRepository.getByAccount(accountKey);
92
+ if (!record || record.deletedAt)
93
+ return;
94
+ await this.deps.slackConnectionRepository.softDelete(accountKey, this.deps.clock.now());
95
+ }
96
+ /** List channels the bot can post to, for the routing picker; [] when not connected. */
97
+ async listChannels(workspaceId) {
98
+ const accountKey = await this.resolveAccountKey(workspaceId);
99
+ const record = await this.deps.slackConnectionRepository.getByAccount(accountKey);
100
+ if (!record || record.deletedAt)
101
+ return [];
102
+ const token = await this.deps.secretCipher.decrypt(record.tokenCipher);
103
+ return this.slack.conversationsList(token);
104
+ }
105
+ /** Encrypt the token + persist the connection, guarding against a cross-account claim. */
106
+ async store(accountKey, info) {
107
+ // Guard: a Slack team already bound to a DIFFERENT account cannot be claimed
108
+ // (account-takeover prevention); reconnecting the SAME account is fine. We
109
+ // ignore the other binding's tombstone — the app is still installed on Slack.
110
+ const claimed = await this.deps.slackConnectionRepository.getByTeam(info.teamId);
111
+ if (claimed && claimed.accountId !== accountKey) {
112
+ throw new ConflictError(`Slack team ${info.teamId} is already connected to another account`);
113
+ }
114
+ const existing = await this.deps.slackConnectionRepository.getByAccount(accountKey);
115
+ const tokenCipher = await this.deps.secretCipher.encrypt(info.token);
116
+ const record = {
117
+ accountId: accountKey,
118
+ teamId: info.teamId,
119
+ teamName: info.teamName,
120
+ teamIconUrl: info.teamIconUrl,
121
+ botUserId: info.botUserId,
122
+ scopesJson: info.scopes.length ? JSON.stringify(info.scopes) : null,
123
+ tokenCipher,
124
+ createdAt: existing && !existing.deletedAt ? existing.createdAt : this.deps.clock.now(),
125
+ deletedAt: null,
126
+ };
127
+ await this.deps.slackConnectionRepository.upsert(record);
128
+ return toConnection(record);
129
+ }
130
+ /** The per-account scope key for a workspace (account id, else the workspace id). */
131
+ async resolveAccountKey(workspaceId) {
132
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
133
+ return (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? workspaceId;
134
+ }
135
+ }
136
+ //# sourceMappingURL=SlackConnectionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackConnectionService.js","sourceRoot":"","sources":["../../../src/modules/slack/SlackConnectionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Clock, WorkspaceRepository } from '@cat-factory/kernel';
2
+ import type { SlackMemberMappingEntry, SlackMemberMappingRepository } from '@cat-factory/kernel';
3
+ export interface SlackMemberMappingServiceDependencies {
4
+ slackMemberMappingRepository: SlackMemberMappingRepository;
5
+ workspaceRepository: WorkspaceRepository;
6
+ clock: Clock;
7
+ }
8
+ export declare class SlackMemberMappingService {
9
+ private readonly deps;
10
+ constructor(deps: SlackMemberMappingServiceDependencies);
11
+ /** The account's mapping entries (empty when unset). */
12
+ get(workspaceId: string): Promise<SlackMemberMappingEntry[]>;
13
+ /** Replace the account's mapping. Last write per GitHub user id wins. */
14
+ update(workspaceId: string, entries: SlackMemberMappingEntry[]): Promise<SlackMemberMappingEntry[]>;
15
+ private resolveAccountKey;
16
+ }
17
+ //# sourceMappingURL=SlackMemberMappingService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackMemberMappingService.d.ts","sourceRoot":"","sources":["../../../src/modules/slack/SlackMemberMappingService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAQhG,MAAM,WAAW,qCAAqC;IACpD,4BAA4B,EAAE,4BAA4B,CAAA;IAC1D,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;CACb;AAED,qBAAa,yBAAyB;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,qCAAqC,EAAI;IAE5E,wDAAwD;IAClD,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAGjE;IAED,yEAAyE;IACnE,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,uBAAuB,EAAE,GACjC,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAQpC;YAEa,iBAAiB;CAIhC"}
@@ -0,0 +1,28 @@
1
+ import { requireWorkspace } from '@cat-factory/kernel';
2
+ export class SlackMemberMappingService {
3
+ deps;
4
+ constructor(deps) {
5
+ this.deps = deps;
6
+ }
7
+ /** The account's mapping entries (empty when unset). */
8
+ async get(workspaceId) {
9
+ const accountKey = await this.resolveAccountKey(workspaceId);
10
+ return this.deps.slackMemberMappingRepository.getByAccount(accountKey);
11
+ }
12
+ /** Replace the account's mapping. Last write per GitHub user id wins. */
13
+ async update(workspaceId, entries) {
14
+ const accountKey = await this.resolveAccountKey(workspaceId);
15
+ // De-duplicate by userId (a stable, deterministic map).
16
+ const byUser = new Map();
17
+ for (const entry of entries)
18
+ byUser.set(entry.userId, entry);
19
+ const deduped = [...byUser.values()];
20
+ await this.deps.slackMemberMappingRepository.upsert(accountKey, deduped, this.deps.clock.now());
21
+ return deduped;
22
+ }
23
+ async resolveAccountKey(workspaceId) {
24
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
25
+ return (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? workspaceId;
26
+ }
27
+ }
28
+ //# sourceMappingURL=SlackMemberMappingService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackMemberMappingService.js","sourceRoot":"","sources":["../../../src/modules/slack/SlackMemberMappingService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAatD,MAAM,OAAO,yBAAyB;IACP,IAAI;IAAjC,YAA6B,IAA2C;oBAA3C,IAAI;IAA0C,CAAC;IAE5E,wDAAwD;IACxD,KAAK,CAAC,GAAG,CAAC,WAAmB;QAC3B,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;IACxE,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,MAAM,CACV,WAAmB,EACnB,OAAkC;QAElC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QAC5D,wDAAwD;QACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAA;QACzD,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAC5D,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;QACpC,MAAM,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;QAC/F,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACjD,MAAM,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAClE,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,WAAW,CAAA;IACpF,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ import type { BlockRepository, Notification, NotificationChannel, SecretCipher, SlackConnectionRepository, SlackMemberMappingRepository, SlackSettingsRepository, WorkspaceRepository } from '@cat-factory/kernel';
2
+ import { SlackApiClient } from './SlackApiClient.js';
3
+ export interface SlackNotificationChannelDependencies {
4
+ workspaceRepository: WorkspaceRepository;
5
+ slackConnectionRepository: SlackConnectionRepository;
6
+ slackSettingsRepository: SlackSettingsRepository;
7
+ slackMemberMappingRepository: SlackMemberMappingRepository;
8
+ /** Resolves a notification's block to read its creator for creator-mentions. */
9
+ blockRepository: BlockRepository;
10
+ secretCipher: SecretCipher;
11
+ /** Slack Web API client; defaults to a fetch-backed one. */
12
+ slackClient?: SlackApiClient;
13
+ /**
14
+ * Optional observability hook invoked when a delivery attempt fails. Delivery is
15
+ * best-effort (a Slack outage/misconfig must never break the notification
16
+ * lifecycle), but a swallowed failure should still be diagnosable — the facades
17
+ * wire this to their structured logger so a broken Slack route (a revoked token,
18
+ * a channel the bot was never invited to) surfaces instead of vanishing.
19
+ */
20
+ onError?: (error: unknown, context: {
21
+ workspaceId: string;
22
+ notificationId: string;
23
+ type: string;
24
+ }) => void;
25
+ }
26
+ export declare class SlackNotificationChannel implements NotificationChannel {
27
+ private readonly deps;
28
+ private readonly slack;
29
+ constructor(deps: SlackNotificationChannelDependencies);
30
+ deliver(workspaceId: string, notification: Notification): Promise<void>;
31
+ private post;
32
+ /**
33
+ * Resolve the Slack member ids to @-mention, scoped to the notification's
34
+ * audience (see {@link resolveMentionTargets}): role-matched mapped members for
35
+ * the type (product people on a requirement review) plus the task's creator. So
36
+ * an engineering notification pings only the person who created the task, and a
37
+ * requirement review pings the product folks who own those decisions — never the
38
+ * whole workspace. Unmapped people are skipped; an empty map means no mentions.
39
+ */
40
+ private resolveMentions;
41
+ /** The internal user id of the block's creator, or null when unknown. */
42
+ private resolveCreator;
43
+ private parseRoutes;
44
+ }
45
+ //# sourceMappingURL=SlackNotificationChannel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackNotificationChannel.d.ts","sourceRoot":"","sources":["../../../src/modules/slack/SlackNotificationChannel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAuBpD,MAAM,WAAW,oCAAoC;IACnD,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,uBAAuB,EAAE,uBAAuB,CAAA;IAChD,4BAA4B,EAAE,4BAA4B,CAAA;IAC1D,gFAAgF;IAChF,eAAe,EAAE,eAAe,CAAA;IAChC,YAAY,EAAE,YAAY,CAAA;IAC1B,4DAA4D;IAC5D,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KACnE,IAAI,CAAA;CACV;AAED,qBAAa,wBAAyB,YAAW,mBAAmB;IAGtD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,YAA6B,IAAI,EAAE,oCAAoC,EAEtE;IAEK,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAc5E;YAEa,IAAI;IA0BlB;;;;;;;OAOG;YACW,eAAe;IAW7B,yEAAyE;YAC3D,cAAc;IAS5B,OAAO,CAAC,WAAW;CAWpB"}
@@ -0,0 +1,84 @@
1
+ import { SlackApiClient } from './SlackApiClient.js';
2
+ import { defaultSlackSettings, renderNotificationMessage, resolveMentionTargets, resolveRoute, } from './slack.logic.js';
3
+ export class SlackNotificationChannel {
4
+ deps;
5
+ slack;
6
+ constructor(deps) {
7
+ this.deps = deps;
8
+ this.slack = deps.slackClient ?? new SlackApiClient();
9
+ }
10
+ async deliver(workspaceId, notification) {
11
+ try {
12
+ await this.post(workspaceId, notification);
13
+ }
14
+ catch (error) {
15
+ // Best-effort: never let a Slack outage/misconfig break the notification
16
+ // lifecycle (CompositeNotificationChannel also isolates us, belt-and-braces).
17
+ // Surface it through the optional observability hook so the failure is
18
+ // diagnosable instead of silently dropped.
19
+ this.deps.onError?.(error, {
20
+ workspaceId,
21
+ notificationId: notification.id,
22
+ type: notification.type,
23
+ });
24
+ }
25
+ }
26
+ async post(workspaceId, notification) {
27
+ const accountKey = (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? workspaceId;
28
+ const connection = await this.deps.slackConnectionRepository.getByAccount(accountKey);
29
+ if (!connection || connection.deletedAt)
30
+ return;
31
+ const settingsRecord = await this.deps.slackSettingsRepository.getByWorkspace(workspaceId);
32
+ const settings = settingsRecord
33
+ ? {
34
+ routes: this.parseRoutes(settingsRecord.routesJson),
35
+ mentionsEnabled: settingsRecord.mentionsEnabled,
36
+ updatedAt: settingsRecord.updatedAt,
37
+ }
38
+ : defaultSlackSettings(0);
39
+ const channel = resolveRoute(settings, notification.type);
40
+ if (!channel)
41
+ return;
42
+ const mentions = settings.mentionsEnabled
43
+ ? await this.resolveMentions(accountKey, workspaceId, notification)
44
+ : [];
45
+ const token = await this.deps.secretCipher.decrypt(connection.tokenCipher);
46
+ const message = renderNotificationMessage(notification, channel, mentions);
47
+ await this.slack.chatPostMessage(token, message);
48
+ }
49
+ /**
50
+ * Resolve the Slack member ids to @-mention, scoped to the notification's
51
+ * audience (see {@link resolveMentionTargets}): role-matched mapped members for
52
+ * the type (product people on a requirement review) plus the task's creator. So
53
+ * an engineering notification pings only the person who created the task, and a
54
+ * requirement review pings the product folks who own those decisions — never the
55
+ * whole workspace. Unmapped people are skipped; an empty map means no mentions.
56
+ */
57
+ async resolveMentions(accountKey, workspaceId, notification) {
58
+ const mapping = await this.deps.slackMemberMappingRepository.getByAccount(accountKey);
59
+ if (mapping.length === 0)
60
+ return [];
61
+ const creatorUserId = await this.resolveCreator(workspaceId, notification.blockId);
62
+ return resolveMentionTargets(notification.type, mapping, creatorUserId);
63
+ }
64
+ /** The internal user id of the block's creator, or null when unknown. */
65
+ async resolveCreator(workspaceId, blockId) {
66
+ if (!blockId)
67
+ return null;
68
+ const block = await this.deps.blockRepository.get(workspaceId, blockId);
69
+ return block?.createdBy ?? null;
70
+ }
71
+ parseRoutes(routesJson) {
72
+ try {
73
+ const parsed = JSON.parse(routesJson);
74
+ if (parsed && typeof parsed === 'object') {
75
+ return parsed;
76
+ }
77
+ }
78
+ catch {
79
+ // fall through to empty
80
+ }
81
+ return {};
82
+ }
83
+ }
84
+ //# sourceMappingURL=SlackNotificationChannel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackNotificationChannel.js","sourceRoot":"","sources":["../../../src/modules/slack/SlackNotificationChannel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Clock, WorkspaceRepository } from '@cat-factory/kernel';
2
+ import type { SlackNotificationSettings, SlackSettingsRepository, UpdateSlackSettingsInput } from '@cat-factory/kernel';
3
+ export interface SlackSettingsServiceDependencies {
4
+ slackSettingsRepository: SlackSettingsRepository;
5
+ workspaceRepository: WorkspaceRepository;
6
+ clock: Clock;
7
+ }
8
+ export declare class SlackSettingsService {
9
+ private readonly deps;
10
+ constructor(deps: SlackSettingsServiceDependencies);
11
+ /** A workspace's settings, falling back to the (no-op) defaults. */
12
+ get(workspaceId: string): Promise<SlackNotificationSettings>;
13
+ /** Replace a workspace's routing settings. */
14
+ update(workspaceId: string, input: UpdateSlackSettingsInput): Promise<SlackNotificationSettings>;
15
+ }
16
+ //# sourceMappingURL=SlackSettingsService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackSettingsService.d.ts","sourceRoot":"","sources":["../../../src/modules/slack/SlackSettingsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EACV,yBAAyB,EAEzB,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAS5B,MAAM,WAAW,gCAAgC;IAC/C,uBAAuB,EAAE,uBAAuB,CAAA;IAChD,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;CACb;AAeD,qBAAa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,gCAAgC,EAAI;IAEvE,oEAAoE;IAC9D,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAIjE;IAED,8CAA8C;IACxC,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAWpC;CACF"}