brains-mcp 1.61.2

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 (350) hide show
  1. package/README.md +95 -0
  2. package/dist/access/projectShares.d.ts +48 -0
  3. package/dist/access/projectShares.d.ts.map +1 -0
  4. package/dist/access/projectShares.js +203 -0
  5. package/dist/access/projectShares.js.map +1 -0
  6. package/dist/access/sharedInstructionsNote.d.ts +37 -0
  7. package/dist/access/sharedInstructionsNote.d.ts.map +1 -0
  8. package/dist/access/sharedInstructionsNote.js +76 -0
  9. package/dist/access/sharedInstructionsNote.js.map +1 -0
  10. package/dist/admin/seedUsers.d.ts +23 -0
  11. package/dist/admin/seedUsers.d.ts.map +1 -0
  12. package/dist/admin/seedUsers.js +168 -0
  13. package/dist/admin/seedUsers.js.map +1 -0
  14. package/dist/api/openapi.d.ts +2 -0
  15. package/dist/api/openapi.d.ts.map +1 -0
  16. package/dist/api/openapi.js +942 -0
  17. package/dist/api/openapi.js.map +1 -0
  18. package/dist/auth/clientStore.d.ts +43 -0
  19. package/dist/auth/clientStore.d.ts.map +1 -0
  20. package/dist/auth/clientStore.js +168 -0
  21. package/dist/auth/clientStore.js.map +1 -0
  22. package/dist/auth/deleteAccount.d.ts +34 -0
  23. package/dist/auth/deleteAccount.d.ts.map +1 -0
  24. package/dist/auth/deleteAccount.js +203 -0
  25. package/dist/auth/deleteAccount.js.map +1 -0
  26. package/dist/auth/oauth.d.ts +118 -0
  27. package/dist/auth/oauth.d.ts.map +1 -0
  28. package/dist/auth/oauth.js +491 -0
  29. package/dist/auth/oauth.js.map +1 -0
  30. package/dist/auth/patStore.d.ts +87 -0
  31. package/dist/auth/patStore.d.ts.map +1 -0
  32. package/dist/auth/patStore.js +308 -0
  33. package/dist/auth/patStore.js.map +1 -0
  34. package/dist/auth/supabase.d.ts +15 -0
  35. package/dist/auth/supabase.d.ts.map +1 -0
  36. package/dist/auth/supabase.js +66 -0
  37. package/dist/auth/supabase.js.map +1 -0
  38. package/dist/auth/tokenStore.d.ts +73 -0
  39. package/dist/auth/tokenStore.d.ts.map +1 -0
  40. package/dist/auth/tokenStore.js +261 -0
  41. package/dist/auth/tokenStore.js.map +1 -0
  42. package/dist/backup/drive.d.ts +24 -0
  43. package/dist/backup/drive.d.ts.map +1 -0
  44. package/dist/backup/drive.js +70 -0
  45. package/dist/backup/drive.js.map +1 -0
  46. package/dist/billing/tierStore.d.ts +59 -0
  47. package/dist/billing/tierStore.d.ts.map +1 -0
  48. package/dist/billing/tierStore.js +152 -0
  49. package/dist/billing/tierStore.js.map +1 -0
  50. package/dist/digest/activityDigest.d.ts +32 -0
  51. package/dist/digest/activityDigest.d.ts.map +1 -0
  52. package/dist/digest/activityDigest.js +299 -0
  53. package/dist/digest/activityDigest.js.map +1 -0
  54. package/dist/digest/digestCron.d.ts +40 -0
  55. package/dist/digest/digestCron.d.ts.map +1 -0
  56. package/dist/digest/digestCron.js +146 -0
  57. package/dist/digest/digestCron.js.map +1 -0
  58. package/dist/digest/digestPreferences.d.ts +25 -0
  59. package/dist/digest/digestPreferences.d.ts.map +1 -0
  60. package/dist/digest/digestPreferences.js +79 -0
  61. package/dist/digest/digestPreferences.js.map +1 -0
  62. package/dist/digest/emailSender.d.ts +20 -0
  63. package/dist/digest/emailSender.d.ts.map +1 -0
  64. package/dist/digest/emailSender.js +35 -0
  65. package/dist/digest/emailSender.js.map +1 -0
  66. package/dist/drive/client.d.ts +43 -0
  67. package/dist/drive/client.d.ts.map +1 -0
  68. package/dist/drive/client.js +282 -0
  69. package/dist/drive/client.js.map +1 -0
  70. package/dist/drive/errors.d.ts +27 -0
  71. package/dist/drive/errors.d.ts.map +1 -0
  72. package/dist/drive/errors.js +51 -0
  73. package/dist/drive/errors.js.map +1 -0
  74. package/dist/drive/fileCache.d.ts +22 -0
  75. package/dist/drive/fileCache.d.ts.map +1 -0
  76. package/dist/drive/fileCache.js +57 -0
  77. package/dist/drive/fileCache.js.map +1 -0
  78. package/dist/governance/accessControl.d.ts +87 -0
  79. package/dist/governance/accessControl.d.ts.map +1 -0
  80. package/dist/governance/accessControl.js +179 -0
  81. package/dist/governance/accessControl.js.map +1 -0
  82. package/dist/html/components.d.ts +8 -0
  83. package/dist/html/components.d.ts.map +1 -0
  84. package/dist/html/components.js +19 -0
  85. package/dist/html/components.js.map +1 -0
  86. package/dist/html/layout.d.ts +7 -0
  87. package/dist/html/layout.d.ts.map +1 -0
  88. package/dist/html/layout.js +24 -0
  89. package/dist/html/layout.js.map +1 -0
  90. package/dist/html/metricsUi.d.ts +8 -0
  91. package/dist/html/metricsUi.d.ts.map +1 -0
  92. package/dist/html/metricsUi.js +271 -0
  93. package/dist/html/metricsUi.js.map +1 -0
  94. package/dist/httpServer.d.ts +19 -0
  95. package/dist/httpServer.d.ts.map +1 -0
  96. package/dist/httpServer.js +3913 -0
  97. package/dist/httpServer.js.map +1 -0
  98. package/dist/index.d.ts +3 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +40 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/jobs/asyncJobRegistry.d.ts +30 -0
  103. package/dist/jobs/asyncJobRegistry.d.ts.map +1 -0
  104. package/dist/jobs/asyncJobRegistry.js +70 -0
  105. package/dist/jobs/asyncJobRegistry.js.map +1 -0
  106. package/dist/observability/metricsQuery.d.ts +41 -0
  107. package/dist/observability/metricsQuery.d.ts.map +1 -0
  108. package/dist/observability/metricsQuery.js +106 -0
  109. package/dist/observability/metricsQuery.js.map +1 -0
  110. package/dist/observability/relevanceSignal.d.ts +26 -0
  111. package/dist/observability/relevanceSignal.d.ts.map +1 -0
  112. package/dist/observability/relevanceSignal.js +95 -0
  113. package/dist/observability/relevanceSignal.js.map +1 -0
  114. package/dist/observability/requestEvents.d.ts +31 -0
  115. package/dist/observability/requestEvents.d.ts.map +1 -0
  116. package/dist/observability/requestEvents.js +54 -0
  117. package/dist/observability/requestEvents.js.map +1 -0
  118. package/dist/observability/slowQueryAlert.d.ts +25 -0
  119. package/dist/observability/slowQueryAlert.d.ts.map +1 -0
  120. package/dist/observability/slowQueryAlert.js +67 -0
  121. package/dist/observability/slowQueryAlert.js.map +1 -0
  122. package/dist/observability/zeroResultTriage.d.ts +22 -0
  123. package/dist/observability/zeroResultTriage.d.ts.map +1 -0
  124. package/dist/observability/zeroResultTriage.js +116 -0
  125. package/dist/observability/zeroResultTriage.js.map +1 -0
  126. package/dist/search/embeddings.d.ts +31 -0
  127. package/dist/search/embeddings.d.ts.map +1 -0
  128. package/dist/search/embeddings.js +91 -0
  129. package/dist/search/embeddings.js.map +1 -0
  130. package/dist/search/fileWatcher.d.ts +28 -0
  131. package/dist/search/fileWatcher.d.ts.map +1 -0
  132. package/dist/search/fileWatcher.js +103 -0
  133. package/dist/search/fileWatcher.js.map +1 -0
  134. package/dist/search/hotQueryCache.d.ts +31 -0
  135. package/dist/search/hotQueryCache.d.ts.map +1 -0
  136. package/dist/search/hotQueryCache.js +103 -0
  137. package/dist/search/hotQueryCache.js.map +1 -0
  138. package/dist/search/postgresIndex.d.ts +439 -0
  139. package/dist/search/postgresIndex.d.ts.map +1 -0
  140. package/dist/search/postgresIndex.js +1610 -0
  141. package/dist/search/postgresIndex.js.map +1 -0
  142. package/dist/search/sqliteIndex.d.ts +66 -0
  143. package/dist/search/sqliteIndex.d.ts.map +1 -0
  144. package/dist/search/sqliteIndex.js +342 -0
  145. package/dist/search/sqliteIndex.js.map +1 -0
  146. package/dist/server.d.ts +43 -0
  147. package/dist/server.d.ts.map +1 -0
  148. package/dist/server.js +532 -0
  149. package/dist/server.js.map +1 -0
  150. package/dist/session/bundle.d.ts +53 -0
  151. package/dist/session/bundle.d.ts.map +1 -0
  152. package/dist/session/bundle.js +97 -0
  153. package/dist/session/bundle.js.map +1 -0
  154. package/dist/storage/client.d.ts +157 -0
  155. package/dist/storage/client.d.ts.map +1 -0
  156. package/dist/storage/client.js +825 -0
  157. package/dist/storage/client.js.map +1 -0
  158. package/dist/tools/addWebhookPattern.d.ts +15 -0
  159. package/dist/tools/addWebhookPattern.d.ts.map +1 -0
  160. package/dist/tools/addWebhookPattern.js +15 -0
  161. package/dist/tools/addWebhookPattern.js.map +1 -0
  162. package/dist/tools/appendLog.d.ts +18 -0
  163. package/dist/tools/appendLog.d.ts.map +1 -0
  164. package/dist/tools/appendLog.js +18 -0
  165. package/dist/tools/appendLog.js.map +1 -0
  166. package/dist/tools/auditIndex.d.ts +15 -0
  167. package/dist/tools/auditIndex.d.ts.map +1 -0
  168. package/dist/tools/auditIndex.js +165 -0
  169. package/dist/tools/auditIndex.js.map +1 -0
  170. package/dist/tools/batchDeletePages.d.ts +18 -0
  171. package/dist/tools/batchDeletePages.d.ts.map +1 -0
  172. package/dist/tools/batchDeletePages.js +143 -0
  173. package/dist/tools/batchDeletePages.js.map +1 -0
  174. package/dist/tools/coerce.d.ts +3 -0
  175. package/dist/tools/coerce.d.ts.map +1 -0
  176. package/dist/tools/coerce.js +12 -0
  177. package/dist/tools/coerce.js.map +1 -0
  178. package/dist/tools/createPage.d.ts +24 -0
  179. package/dist/tools/createPage.d.ts.map +1 -0
  180. package/dist/tools/createPage.js +99 -0
  181. package/dist/tools/createPage.js.map +1 -0
  182. package/dist/tools/createPages.d.ts +51 -0
  183. package/dist/tools/createPages.d.ts.map +1 -0
  184. package/dist/tools/createPages.js +91 -0
  185. package/dist/tools/createPages.js.map +1 -0
  186. package/dist/tools/deletePage.d.ts +19 -0
  187. package/dist/tools/deletePage.d.ts.map +1 -0
  188. package/dist/tools/deletePage.js +127 -0
  189. package/dist/tools/deletePage.js.map +1 -0
  190. package/dist/tools/exportWiki.d.ts +48 -0
  191. package/dist/tools/exportWiki.d.ts.map +1 -0
  192. package/dist/tools/exportWiki.js +233 -0
  193. package/dist/tools/exportWiki.js.map +1 -0
  194. package/dist/tools/extractLinks.d.ts +14 -0
  195. package/dist/tools/extractLinks.d.ts.map +1 -0
  196. package/dist/tools/extractLinks.js +25 -0
  197. package/dist/tools/extractLinks.js.map +1 -0
  198. package/dist/tools/generateClaudeInstructions.d.ts +27 -0
  199. package/dist/tools/generateClaudeInstructions.d.ts.map +1 -0
  200. package/dist/tools/generateClaudeInstructions.js +263 -0
  201. package/dist/tools/generateClaudeInstructions.js.map +1 -0
  202. package/dist/tools/generateGptInstructions.d.ts +27 -0
  203. package/dist/tools/generateGptInstructions.d.ts.map +1 -0
  204. package/dist/tools/generateGptInstructions.js +208 -0
  205. package/dist/tools/generateGptInstructions.js.map +1 -0
  206. package/dist/tools/getBacklinks.d.ts +12 -0
  207. package/dist/tools/getBacklinks.d.ts.map +1 -0
  208. package/dist/tools/getBacklinks.js +13 -0
  209. package/dist/tools/getBacklinks.js.map +1 -0
  210. package/dist/tools/getRelated.d.ts +12 -0
  211. package/dist/tools/getRelated.d.ts.map +1 -0
  212. package/dist/tools/getRelated.js +13 -0
  213. package/dist/tools/getRelated.js.map +1 -0
  214. package/dist/tools/healthCheck.d.ts +5 -0
  215. package/dist/tools/healthCheck.d.ts.map +1 -0
  216. package/dist/tools/healthCheck.js +167 -0
  217. package/dist/tools/healthCheck.js.map +1 -0
  218. package/dist/tools/importBrainSource.d.ts +36 -0
  219. package/dist/tools/importBrainSource.d.ts.map +1 -0
  220. package/dist/tools/importBrainSource.js +314 -0
  221. package/dist/tools/importBrainSource.js.map +1 -0
  222. package/dist/tools/importWikiZip.d.ts +60 -0
  223. package/dist/tools/importWikiZip.d.ts.map +1 -0
  224. package/dist/tools/importWikiZip.js +379 -0
  225. package/dist/tools/importWikiZip.js.map +1 -0
  226. package/dist/tools/initProject.d.ts +34 -0
  227. package/dist/tools/initProject.d.ts.map +1 -0
  228. package/dist/tools/initProject.js +443 -0
  229. package/dist/tools/initProject.js.map +1 -0
  230. package/dist/tools/instructionContext.d.ts +60 -0
  231. package/dist/tools/instructionContext.d.ts.map +1 -0
  232. package/dist/tools/instructionContext.js +210 -0
  233. package/dist/tools/instructionContext.js.map +1 -0
  234. package/dist/tools/listArchivedPages.d.ts +17 -0
  235. package/dist/tools/listArchivedPages.d.ts.map +1 -0
  236. package/dist/tools/listArchivedPages.js +51 -0
  237. package/dist/tools/listArchivedPages.js.map +1 -0
  238. package/dist/tools/listPages.d.ts +28 -0
  239. package/dist/tools/listPages.d.ts.map +1 -0
  240. package/dist/tools/listPages.js +166 -0
  241. package/dist/tools/listPages.js.map +1 -0
  242. package/dist/tools/listWebhooks.d.ts +7 -0
  243. package/dist/tools/listWebhooks.d.ts.map +1 -0
  244. package/dist/tools/listWebhooks.js +10 -0
  245. package/dist/tools/listWebhooks.js.map +1 -0
  246. package/dist/tools/movePage.d.ts +18 -0
  247. package/dist/tools/movePage.d.ts.map +1 -0
  248. package/dist/tools/movePage.js +182 -0
  249. package/dist/tools/movePage.js.map +1 -0
  250. package/dist/tools/pageHistory.d.ts +36 -0
  251. package/dist/tools/pageHistory.d.ts.map +1 -0
  252. package/dist/tools/pageHistory.js +60 -0
  253. package/dist/tools/pageHistory.js.map +1 -0
  254. package/dist/tools/patchPage.d.ts +141 -0
  255. package/dist/tools/patchPage.d.ts.map +1 -0
  256. package/dist/tools/patchPage.js +287 -0
  257. package/dist/tools/patchPage.js.map +1 -0
  258. package/dist/tools/pullSessionBundle.d.ts +34 -0
  259. package/dist/tools/pullSessionBundle.d.ts.map +1 -0
  260. package/dist/tools/pullSessionBundle.js +432 -0
  261. package/dist/tools/pullSessionBundle.js.map +1 -0
  262. package/dist/tools/purgeWiki.d.ts +23 -0
  263. package/dist/tools/purgeWiki.d.ts.map +1 -0
  264. package/dist/tools/purgeWiki.js +113 -0
  265. package/dist/tools/purgeWiki.js.map +1 -0
  266. package/dist/tools/pushSessionBundle.d.ts +139 -0
  267. package/dist/tools/pushSessionBundle.d.ts.map +1 -0
  268. package/dist/tools/pushSessionBundle.js +282 -0
  269. package/dist/tools/pushSessionBundle.js.map +1 -0
  270. package/dist/tools/readGovernanceAudit.d.ts +17 -0
  271. package/dist/tools/readGovernanceAudit.d.ts.map +1 -0
  272. package/dist/tools/readGovernanceAudit.js +21 -0
  273. package/dist/tools/readGovernanceAudit.js.map +1 -0
  274. package/dist/tools/readPage.d.ts +13 -0
  275. package/dist/tools/readPage.d.ts.map +1 -0
  276. package/dist/tools/readPage.js +46 -0
  277. package/dist/tools/readPage.js.map +1 -0
  278. package/dist/tools/removeWebhook.d.ts +14 -0
  279. package/dist/tools/removeWebhook.d.ts.map +1 -0
  280. package/dist/tools/removeWebhook.js +12 -0
  281. package/dist/tools/removeWebhook.js.map +1 -0
  282. package/dist/tools/resolveProject.d.ts +58 -0
  283. package/dist/tools/resolveProject.d.ts.map +1 -0
  284. package/dist/tools/resolveProject.js +175 -0
  285. package/dist/tools/resolveProject.js.map +1 -0
  286. package/dist/tools/restorePage.d.ts +47 -0
  287. package/dist/tools/restorePage.d.ts.map +1 -0
  288. package/dist/tools/restorePage.js +111 -0
  289. package/dist/tools/restorePage.js.map +1 -0
  290. package/dist/tools/searchPages.d.ts +28 -0
  291. package/dist/tools/searchPages.d.ts.map +1 -0
  292. package/dist/tools/searchPages.js +372 -0
  293. package/dist/tools/searchPages.js.map +1 -0
  294. package/dist/tools/setWebhook.d.ts +30 -0
  295. package/dist/tools/setWebhook.d.ts.map +1 -0
  296. package/dist/tools/setWebhook.js +30 -0
  297. package/dist/tools/setWebhook.js.map +1 -0
  298. package/dist/tools/shareProject.d.ts +25 -0
  299. package/dist/tools/shareProject.d.ts.map +1 -0
  300. package/dist/tools/shareProject.js +56 -0
  301. package/dist/tools/shareProject.js.map +1 -0
  302. package/dist/tools/suggestLinks.d.ts +29 -0
  303. package/dist/tools/suggestLinks.d.ts.map +1 -0
  304. package/dist/tools/suggestLinks.js +214 -0
  305. package/dist/tools/suggestLinks.js.map +1 -0
  306. package/dist/tools/updatePage.d.ts +27 -0
  307. package/dist/tools/updatePage.d.ts.map +1 -0
  308. package/dist/tools/updatePage.js +118 -0
  309. package/dist/tools/updatePage.js.map +1 -0
  310. package/dist/webhooks/dispatch.d.ts +49 -0
  311. package/dist/webhooks/dispatch.d.ts.map +1 -0
  312. package/dist/webhooks/dispatch.js +159 -0
  313. package/dist/webhooks/dispatch.js.map +1 -0
  314. package/dist/webhooks/webhookRules.d.ts +34 -0
  315. package/dist/webhooks/webhookRules.d.ts.map +1 -0
  316. package/dist/webhooks/webhookRules.js +129 -0
  317. package/dist/webhooks/webhookRules.js.map +1 -0
  318. package/dist/wiki/frontmatter.d.ts +45 -0
  319. package/dist/wiki/frontmatter.d.ts.map +1 -0
  320. package/dist/wiki/frontmatter.js +68 -0
  321. package/dist/wiki/frontmatter.js.map +1 -0
  322. package/dist/wiki/index.d.ts +22 -0
  323. package/dist/wiki/index.d.ts.map +1 -0
  324. package/dist/wiki/index.js +203 -0
  325. package/dist/wiki/index.js.map +1 -0
  326. package/dist/wiki/ingestPolicy.d.ts +18 -0
  327. package/dist/wiki/ingestPolicy.d.ts.map +1 -0
  328. package/dist/wiki/ingestPolicy.js +64 -0
  329. package/dist/wiki/ingestPolicy.js.map +1 -0
  330. package/dist/wiki/layoutLint.d.ts +51 -0
  331. package/dist/wiki/layoutLint.d.ts.map +1 -0
  332. package/dist/wiki/layoutLint.js +146 -0
  333. package/dist/wiki/layoutLint.js.map +1 -0
  334. package/dist/wiki/links.d.ts +6 -0
  335. package/dist/wiki/links.d.ts.map +1 -0
  336. package/dist/wiki/links.js +45 -0
  337. package/dist/wiki/links.js.map +1 -0
  338. package/dist/wiki/log.d.ts +58 -0
  339. package/dist/wiki/log.d.ts.map +1 -0
  340. package/dist/wiki/log.js +126 -0
  341. package/dist/wiki/log.js.map +1 -0
  342. package/dist/wiki/path.d.ts +8 -0
  343. package/dist/wiki/path.d.ts.map +1 -0
  344. package/dist/wiki/path.js +25 -0
  345. package/dist/wiki/path.js.map +1 -0
  346. package/dist/wiki/storageLimits.d.ts +163 -0
  347. package/dist/wiki/storageLimits.d.ts.map +1 -0
  348. package/dist/wiki/storageLimits.js +281 -0
  349. package/dist/wiki/storageLimits.js.map +1 -0
  350. package/package.json +81 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * activityDigest.ts
3
+ *
4
+ * Gathers weekly activity signals from a user's wiki storage and generates
5
+ * the digest markdown content and email body.
6
+ *
7
+ * Signals collected:
8
+ * - Pages created / modified (from log.md events + file listing)
9
+ * - Decisions made (pages tagged "decision" modified this week)
10
+ * - Open loops closed (pages tagged "open-loop" with status resolved)
11
+ * - Active tags (tags appearing in recently modified pages)
12
+ */
13
+ import matter from "gray-matter";
14
+ import { getPostgresSearchIndex } from "../search/postgresIndex.js";
15
+ // ─── ISO week helpers ─────────────────────────────────────────────────────────
16
+ export function isoWeekLabel(date) {
17
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
18
+ const dayNum = d.getUTCDay() || 7;
19
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
20
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
21
+ const week = Math.ceil(((d.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
22
+ return `${d.getUTCFullYear()}-${String(week).padStart(2, "0")}`;
23
+ }
24
+ function weekRangeLabel(date) {
25
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
26
+ const dow = d.getUTCDay() || 7; // Mon=1..Sun=7
27
+ const monday = new Date(d);
28
+ monday.setUTCDate(d.getUTCDate() - (dow - 1));
29
+ const sunday = new Date(monday);
30
+ sunday.setUTCDate(monday.getUTCDate() + 6);
31
+ const fmt = (dt) => dt.toLocaleDateString("en-US", {
32
+ month: "short",
33
+ day: "numeric",
34
+ year: "numeric",
35
+ timeZone: "UTC",
36
+ });
37
+ if (monday.getUTCFullYear() === sunday.getUTCFullYear()) {
38
+ const start = monday.toLocaleDateString("en-US", {
39
+ month: "short",
40
+ day: "numeric",
41
+ timeZone: "UTC",
42
+ });
43
+ return `${start} – ${fmt(sunday)}`;
44
+ }
45
+ return `${fmt(monday)} – ${fmt(sunday)}`;
46
+ }
47
+ function parseLogEntries(logContent) {
48
+ const events = [];
49
+ for (const line of logContent.split("\n")) {
50
+ // Format: ## [YYYY-MM-DD] type | Title
51
+ const m = line.match(/^##\s+\[(\d{4}-\d{2}-\d{2})\]\s+(\w+)\s+\|\s+(.+)$/);
52
+ if (m)
53
+ events.push({ date: m[1], type: m[2].toLowerCase(), title: m[3].trim() });
54
+ }
55
+ return events;
56
+ }
57
+ function extractSignals(name, content) {
58
+ let fm = {};
59
+ try {
60
+ fm = (matter(content).data ?? {});
61
+ }
62
+ catch {
63
+ // non-fatal — use defaults
64
+ }
65
+ const tags = Array.isArray(fm["tags"])
66
+ ? fm["tags"].filter((t) => typeof t === "string")
67
+ : [];
68
+ const status = typeof fm["status"] === "string" ? fm["status"] : "";
69
+ const title = typeof fm["title"] === "string" ? fm["title"] : name;
70
+ return {
71
+ name,
72
+ title,
73
+ tags,
74
+ status,
75
+ isDecision: tags.includes("decision") || name.startsWith("decisions/"),
76
+ isOpenLoop: tags.includes("open-loop"),
77
+ isClosed: status === "resolved" || status === "done" || status === "archived",
78
+ };
79
+ }
80
+ // ─── Main gather function ─────────────────────────────────────────────────────
81
+ const SKIP_NAMES = new Set(["log.md", "index.md"]);
82
+ export async function gatherDigestSignals(drive, now = new Date(), userId = "") {
83
+ const cutoff = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
84
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
85
+ // ── 1. Activity events ─────────────────────────────────────────────────────
86
+ //
87
+ // Postgres-backed deployments read the indexed wiki_events table; log.md is
88
+ // no longer appended there (GH #417). Falls through to parsing log.md when
89
+ // Postgres is unavailable or the query fails, which also covers local/Drive
90
+ // backends and any wiki whose history predates the cutover.
91
+ let logEvents = [];
92
+ const events = await getPostgresSearchIndex()
93
+ .listEvents(userId, cutoffStr)
94
+ .catch(() => null);
95
+ if (events) {
96
+ logEvents = events;
97
+ }
98
+ else {
99
+ try {
100
+ const logFile = await drive.readFile("log.md");
101
+ logEvents = parseLogEntries(logFile.content).filter((e) => e.date >= cutoffStr);
102
+ }
103
+ catch {
104
+ // log.md missing — not a fatal error
105
+ }
106
+ }
107
+ const createdNames = new Set(logEvents.filter((e) => e.type === "create").map((e) => e.title));
108
+ // ── 2. List files modified in the last 7 days ──────────────────────────────
109
+ let allFiles = [];
110
+ try {
111
+ allFiles = (await drive.listFiles()).filter((f) => !SKIP_NAMES.has(f.name) && f.modifiedTime >= cutoff.toISOString());
112
+ }
113
+ catch {
114
+ // storage listing failed — non-fatal
115
+ }
116
+ // ── 3. Read frontmatter for recently modified pages ───────────────────────
117
+ const pageSignals = [];
118
+ await Promise.allSettled(allFiles.slice(0, 100).map(async (f) => {
119
+ try {
120
+ const file = await drive.readFile(f.name);
121
+ pageSignals.push(extractSignals(f.name, file.content));
122
+ }
123
+ catch {
124
+ // skip unreadable pages
125
+ }
126
+ }));
127
+ // ── 4. Build signal sets ──────────────────────────────────────────────────
128
+ const pagesCreatedSet = new Set();
129
+ const pagesModifiedSet = new Set();
130
+ const decisionsMade = [];
131
+ const loopsClosed = [];
132
+ const tagCount = new Map();
133
+ for (const ps of pageSignals) {
134
+ const isNew = createdNames.has(ps.name) || createdNames.has(ps.title);
135
+ if (isNew) {
136
+ pagesCreatedSet.add(ps.name);
137
+ }
138
+ else {
139
+ pagesModifiedSet.add(ps.name);
140
+ }
141
+ if (ps.isDecision)
142
+ decisionsMade.push(ps.title);
143
+ if (ps.isOpenLoop && ps.isClosed)
144
+ loopsClosed.push(ps.title);
145
+ for (const tag of ps.tags) {
146
+ tagCount.set(tag, (tagCount.get(tag) ?? 0) + 1);
147
+ }
148
+ }
149
+ // Fallback: use log event counts for creates/updates if file listing was empty
150
+ if (pagesCreatedSet.size === 0 && pagesModifiedSet.size === 0) {
151
+ for (const e of logEvents) {
152
+ if (e.type === "create")
153
+ pagesCreatedSet.add(e.title);
154
+ else if (e.type === "update" || e.type === "ingest")
155
+ pagesModifiedSet.add(e.title);
156
+ }
157
+ }
158
+ const activeTags = [...tagCount.entries()]
159
+ .map(([tag, count]) => ({ tag, count }))
160
+ .sort((a, b) => b.count - a.count)
161
+ .slice(0, 10);
162
+ return {
163
+ weekLabel: isoWeekLabel(now),
164
+ weekRange: weekRangeLabel(now),
165
+ pagesCreated: [...pagesCreatedSet],
166
+ pagesModified: [...pagesModifiedSet],
167
+ decisionsMade,
168
+ loopsClosed,
169
+ activeTags,
170
+ totalEvents: logEvents.length,
171
+ };
172
+ }
173
+ // ─── Markdown page content ────────────────────────────────────────────────────
174
+ export function buildDigestMarkdown(signals, dateStr) {
175
+ const { weekLabel, weekRange, pagesCreated, pagesModified, decisionsMade, loopsClosed, activeTags } = signals;
176
+ const listItems = (items) => items.length > 0 ? items.map((i) => `- ${i}`).join("\n") : "_None this week._";
177
+ const tagStr = activeTags.length > 0
178
+ ? activeTags.map((t) => `\`${t.tag}\` (${t.count})`).join(", ")
179
+ : "_No tagged pages this week._";
180
+ return `---
181
+ title: Brain Digest — Week ${weekLabel}
182
+ updated: ${dateStr}
183
+ tags: [digest, weekly]
184
+ summary: Weekly brain activity for ${weekRange}.
185
+ ---
186
+
187
+ # Brain Digest — Week ${weekLabel}
188
+
189
+ **${weekRange}**
190
+
191
+ ## Activity Summary
192
+
193
+ - **${pagesCreated.length}** page${pagesCreated.length === 1 ? "" : "s"} created
194
+ - **${pagesModified.length}** page${pagesModified.length === 1 ? "" : "s"} updated
195
+
196
+ ## New Pages
197
+
198
+ ${listItems(pagesCreated)}
199
+
200
+ ## Decisions Made
201
+
202
+ ${listItems(decisionsMade)}
203
+
204
+ ## Loops Closed
205
+
206
+ ${listItems(loopsClosed)}
207
+
208
+ ## Active Tags
209
+
210
+ ${tagStr}
211
+
212
+ ---
213
+ _Generated automatically by brains weekly digest._
214
+ `;
215
+ }
216
+ // ─── Email body builders ──────────────────────────────────────────────────────
217
+ export function buildDigestHtml(signals) {
218
+ const { weekLabel, weekRange, pagesCreated, pagesModified, decisionsMade, loopsClosed, activeTags } = signals;
219
+ const li = (items) => items.length > 0
220
+ ? `<ul>${items.map((i) => `<li>${escHtml(i)}</li>`).join("")}</ul>`
221
+ : `<p><em>None this week.</em></p>`;
222
+ const tagChips = activeTags.length > 0
223
+ ? activeTags
224
+ .map((t) => `<span style="display:inline-block;background:#f0f0f0;border-radius:4px;padding:2px 8px;margin:2px;font-size:13px;">${escHtml(t.tag)} <strong>${t.count}</strong></span>`)
225
+ .join(" ")
226
+ : "<em>No tagged pages this week.</em>";
227
+ return `<!DOCTYPE html>
228
+ <html lang="en">
229
+ <head>
230
+ <meta charset="utf-8"/>
231
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
232
+ <title>Brain Digest — Week ${escHtml(weekLabel)}</title>
233
+ </head>
234
+ <body style="font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;padding:24px;color:#111;">
235
+ <h1 style="font-size:22px;margin-bottom:4px;">🧠 Brain Digest — Week ${escHtml(weekLabel)}</h1>
236
+ <p style="color:#666;margin-top:0;">${escHtml(weekRange)}</p>
237
+
238
+ <table style="width:100%;border-collapse:collapse;margin:16px 0;">
239
+ <tr>
240
+ <td style="padding:12px;background:#f9f9f9;border-radius:6px;text-align:center;width:50%;">
241
+ <div style="font-size:28px;font-weight:bold;">${pagesCreated.length}</div>
242
+ <div style="font-size:13px;color:#555;">Pages created</div>
243
+ </td>
244
+ <td style="width:8px;"></td>
245
+ <td style="padding:12px;background:#f9f9f9;border-radius:6px;text-align:center;width:50%;">
246
+ <div style="font-size:28px;font-weight:bold;">${pagesModified.length}</div>
247
+ <div style="font-size:13px;color:#555;">Pages updated</div>
248
+ </td>
249
+ </tr>
250
+ </table>
251
+
252
+ ${pagesCreated.length > 0 ? `<h2 style="font-size:16px;">New Pages</h2>${li(pagesCreated)}` : ""}
253
+
254
+ ${decisionsMade.length > 0 ? `<h2 style="font-size:16px;">Decisions Made</h2>${li(decisionsMade)}` : ""}
255
+
256
+ ${loopsClosed.length > 0 ? `<h2 style="font-size:16px;">Loops Closed</h2>${li(loopsClosed)}` : ""}
257
+
258
+ ${activeTags.length > 0 ? `<h2 style="font-size:16px;">Active Tags</h2><p>${tagChips}</p>` : ""}
259
+
260
+ <hr style="border:none;border-top:1px solid #eee;margin:24px 0;"/>
261
+ <p style="font-size:12px;color:#999;">
262
+ You're receiving this because you opted in to the weekly brain digest.<br/>
263
+ Manage your preferences in your brains account settings.
264
+ </p>
265
+ </body>
266
+ </html>`;
267
+ }
268
+ export function buildDigestText(signals) {
269
+ const { weekLabel, weekRange, pagesCreated, pagesModified, decisionsMade, loopsClosed, activeTags } = signals;
270
+ const section = (title, items) => items.length > 0
271
+ ? `${title}\n${items.map((i) => ` • ${i}`).join("\n")}\n`
272
+ : "";
273
+ return [
274
+ `Brain Digest — Week ${weekLabel}`,
275
+ weekRange,
276
+ "",
277
+ `Activity: ${pagesCreated.length} created, ${pagesModified.length} updated`,
278
+ "",
279
+ section("New Pages:", pagesCreated),
280
+ section("Decisions Made:", decisionsMade),
281
+ section("Loops Closed:", loopsClosed),
282
+ activeTags.length > 0
283
+ ? `Active Tags: ${activeTags.map((t) => `${t.tag} (${t.count})`).join(", ")}`
284
+ : "",
285
+ "",
286
+ "—",
287
+ "Manage preferences in your brains account settings.",
288
+ ]
289
+ .filter((l) => l !== undefined)
290
+ .join("\n");
291
+ }
292
+ function escHtml(s) {
293
+ return s
294
+ .replace(/&/g, "&amp;")
295
+ .replace(/</g, "&lt;")
296
+ .replace(/>/g, "&gt;")
297
+ .replace(/"/g, "&quot;");
298
+ }
299
+ //# sourceMappingURL=activityDigest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activityDigest.js","sourceRoot":"","sources":["../../src/digest/activityDigest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * digestCron.ts
3
+ *
4
+ * Weekly brain activity digest — cron orchestrator.
5
+ *
6
+ * Schedule: Sunday 08:00 UTC (aligned with brainspace week-end review).
7
+ * Reads opted-in users from `digest_preferences`, generates a digest for
8
+ * each user's wiki, emails them via Resend, and saves the digest page to
9
+ * `history/digest-YYYY-WW.md` in their scoped storage.
10
+ *
11
+ * Phase 1 acceptance:
12
+ * - CRON fires Sunday 08:00 UTC
13
+ * - Digest covers prior 7 days
14
+ * - Email sent to opted-in users
15
+ * - Digest saved to history/ namespace
16
+ * - Manual trigger via runDigestForUser()
17
+ */
18
+ import { type StorageClient } from "../storage/client.js";
19
+ export declare function msUntilNextSundayAt8UTC(now?: Date): number;
20
+ interface DigestConfig {
21
+ supabaseUrl: string;
22
+ serviceRoleKey: string;
23
+ resendApiKey: string;
24
+ fromEmail: string;
25
+ fromName: string;
26
+ }
27
+ export interface DigestResult {
28
+ userId: string;
29
+ email: string;
30
+ skipped: boolean;
31
+ skipReason?: string;
32
+ pageSaved?: string;
33
+ emailSent: boolean;
34
+ error?: string;
35
+ }
36
+ export declare function runDigestForUser(userId: string, userEmail: string, userDrive: StorageClient, config: DigestConfig, now?: Date): Promise<DigestResult>;
37
+ export declare function runWeeklyDigest(baseDrive: StorageClient, config: DigestConfig, now?: Date): Promise<DigestResult[]>;
38
+ export declare function startDigestCron(baseDrive: StorageClient, config: DigestConfig): () => void;
39
+ export {};
40
+ //# sourceMappingURL=digestCron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digestCron.d.ts","sourceRoot":"","sources":["../../src/digest/digestCron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAWrF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,IAAiB,GAAG,MAAM,CAiBtE;AAID,UAAU,YAAY;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,YAAY,EACpB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,YAAY,CAAC,CAyDvB;AAID,wBAAsB,eAAe,CACnC,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,YAAY,EACpB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC,CAuCzB;AAID,wBAAgB,eAAe,CAC7B,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,YAAY,GACnB,MAAM,IAAI,CA4BZ"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * digestCron.ts
3
+ *
4
+ * Weekly brain activity digest — cron orchestrator.
5
+ *
6
+ * Schedule: Sunday 08:00 UTC (aligned with brainspace week-end review).
7
+ * Reads opted-in users from `digest_preferences`, generates a digest for
8
+ * each user's wiki, emails them via Resend, and saves the digest page to
9
+ * `history/digest-YYYY-WW.md` in their scoped storage.
10
+ *
11
+ * Phase 1 acceptance:
12
+ * - CRON fires Sunday 08:00 UTC
13
+ * - Digest covers prior 7 days
14
+ * - Email sent to opted-in users
15
+ * - Digest saved to history/ namespace
16
+ * - Manual trigger via runDigestForUser()
17
+ */
18
+ import { gatherDigestSignals, buildDigestMarkdown, buildDigestHtml, buildDigestText, isoWeekLabel, } from "./activityDigest.js";
19
+ import { sendDigestEmail } from "./emailSender.js";
20
+ import { listEnabledPreferences, markDigestSent, } from "./digestPreferences.js";
21
+ import { createScopedStorageClient } from "../storage/client.js";
22
+ import { DriveNotFoundError } from "../drive/errors.js";
23
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
24
+ function subjectToPathKey(subject) {
25
+ const t = subject.trim().toLowerCase();
26
+ const n = t.replace(/[^a-z0-9._-]/g, "_").replace(/^_+|_+$/g, "");
27
+ return n.slice(0, 96) || "user";
28
+ }
29
+ export function msUntilNextSundayAt8UTC(now = new Date()) {
30
+ const candidate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 8, 0, 0, 0));
31
+ // Advance to Sunday (0 = Sunday in getUTCDay)
32
+ const dow = candidate.getUTCDay();
33
+ candidate.setUTCDate(candidate.getUTCDate() + ((7 - dow) % 7));
34
+ // If that Sunday has already passed, go to the next one
35
+ if (candidate.getTime() <= now.getTime()) {
36
+ candidate.setUTCDate(candidate.getUTCDate() + 7);
37
+ }
38
+ return candidate.getTime() - now.getTime();
39
+ }
40
+ export async function runDigestForUser(userId, userEmail, userDrive, config, now = new Date()) {
41
+ const base = { userId, email: userEmail, skipped: false, emailSent: false };
42
+ try {
43
+ // ── 1. Gather signals ────────────────────────────────────────────────────
44
+ const signals = await gatherDigestSignals(userDrive, now, userId);
45
+ const hasActivity = signals.pagesCreated.length > 0 ||
46
+ signals.pagesModified.length > 0 ||
47
+ signals.totalEvents > 0;
48
+ if (!hasActivity) {
49
+ return { ...base, skipped: true, skipReason: "no activity this week" };
50
+ }
51
+ // ── 2. Build content ─────────────────────────────────────────────────────
52
+ const dateStr = now.toISOString().slice(0, 10);
53
+ const weekLabel = isoWeekLabel(now);
54
+ const pageName = `history/digest-${weekLabel}.md`;
55
+ const markdownContent = buildDigestMarkdown(signals, dateStr);
56
+ // ── 3. Save digest page ──────────────────────────────────────────────────
57
+ try {
58
+ await userDrive.createFile(pageName, markdownContent);
59
+ }
60
+ catch {
61
+ // Page already exists (e.g. re-run same week) — update it.
62
+ try {
63
+ const existing = await userDrive.readFile(pageName);
64
+ await userDrive.updateFile(existing.fileId, markdownContent, existing.revision);
65
+ }
66
+ catch (updateErr) {
67
+ if (!(updateErr instanceof DriveNotFoundError)) {
68
+ console.error(`[digest] failed to save page for ${userId}:`, updateErr.message);
69
+ }
70
+ }
71
+ }
72
+ base.pageSaved = pageName;
73
+ // ── 4. Send email ────────────────────────────────────────────────────────
74
+ if (!config.resendApiKey) {
75
+ console.warn("[digest] RESEND_API_KEY not set — skipping email send");
76
+ return { ...base, skipped: false, emailSent: false };
77
+ }
78
+ const subject = `Your Brain Digest — Week ${weekLabel}`;
79
+ await sendDigestEmail(config.resendApiKey, config.fromEmail, config.fromName, {
80
+ to: userEmail,
81
+ subject,
82
+ html: buildDigestHtml(signals),
83
+ text: buildDigestText(signals),
84
+ });
85
+ return { ...base, emailSent: true };
86
+ }
87
+ catch (err) {
88
+ return { ...base, error: err.message };
89
+ }
90
+ }
91
+ // ─── Batch runner ─────────────────────────────────────────────────────────────
92
+ export async function runWeeklyDigest(baseDrive, config, now = new Date()) {
93
+ const prefs = await listEnabledPreferences(config.supabaseUrl, config.serviceRoleKey);
94
+ if (prefs.length === 0) {
95
+ console.log("[digest] no opted-in users — skipping");
96
+ return [];
97
+ }
98
+ console.log(`[digest] running for ${prefs.length} user(s)`);
99
+ const results = [];
100
+ for (const pref of prefs) {
101
+ const subjectKey = subjectToPathKey(pref.user_id);
102
+ const userDrive = createScopedStorageClient(baseDrive, `users/${subjectKey}`);
103
+ const result = await runDigestForUser(pref.user_id, pref.email, userDrive, config, now);
104
+ results.push(result);
105
+ if (result.emailSent) {
106
+ await markDigestSent(config.supabaseUrl, config.serviceRoleKey, pref.user_id, now.toISOString()).catch((err) => {
107
+ console.warn("[digest] failed to mark digest sent:", err.message);
108
+ });
109
+ }
110
+ const status = result.error
111
+ ? `error: ${result.error}`
112
+ : result.skipped
113
+ ? `skipped (${result.skipReason})`
114
+ : result.emailSent
115
+ ? `sent → ${pref.email}`
116
+ : `page saved, no email`;
117
+ console.log(`[digest] ${pref.user_id}: ${status}`);
118
+ }
119
+ return results;
120
+ }
121
+ // ─── Cron scheduler ───────────────────────────────────────────────────────────
122
+ export function startDigestCron(baseDrive, config) {
123
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
124
+ const run = () => {
125
+ runWeeklyDigest(baseDrive, config).catch((err) => {
126
+ console.error("[digest] unhandled cron error:", err.message);
127
+ });
128
+ };
129
+ const delay = msUntilNextSundayAt8UTC();
130
+ console.log(`[digest] cron scheduled — next run in ${Math.round(delay / 3_600_000)}h (Sunday 08:00 UTC)`);
131
+ let weeklyTimer = null;
132
+ const initialTimer = setTimeout(() => {
133
+ run();
134
+ weeklyTimer = setInterval(run, WEEK_MS);
135
+ if (typeof weeklyTimer.unref === "function")
136
+ weeklyTimer.unref();
137
+ }, delay);
138
+ if (typeof initialTimer.unref === "function")
139
+ initialTimer.unref();
140
+ return () => {
141
+ clearTimeout(initialTimer);
142
+ if (weeklyTimer)
143
+ clearInterval(weeklyTimer);
144
+ };
145
+ }
146
+ //# sourceMappingURL=digestCron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digestCron.js","sourceRoot":"","sources":["../../src/digest/digestCron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,yBAAyB,EAAsB,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,gFAAgF;AAEhF,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAY,IAAI,IAAI,EAAE;IAC5D,MAAM,SAAS,GAAG,IAAI,IAAI,CACxB,IAAI,CAAC,GAAG,CACN,GAAG,CAAC,cAAc,EAAE,EACpB,GAAG,CAAC,WAAW,EAAE,EACjB,GAAG,CAAC,UAAU,EAAE,EAChB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CACX,CACF,CAAC;IACF,8CAA8C;IAC9C,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC;IAClC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC/D,wDAAwD;IACxD,IAAI,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACzC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;AAC7C,CAAC;AAsBD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,SAAiB,EACjB,SAAwB,EACxB,MAAoB,EACpB,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,IAAI,GAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAE1F,IAAI,CAAC;QACH,4EAA4E;QAC5E,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAElE,MAAM,WAAW,GACf,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YAC/B,OAAO,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YAChC,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;QAE1B,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,uBAAuB,EAAE,CAAC;QACzE,CAAC;QAED,4EAA4E;QAC5E,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,kBAAkB,SAAS,KAAK,CAAC;QAClD,MAAM,eAAe,GAAG,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE9D,4EAA4E;QAC5E,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;YAC3D,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACpD,MAAM,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAClF,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;gBACnB,IAAI,CAAC,CAAC,SAAS,YAAY,kBAAkB,CAAC,EAAE,CAAC;oBAC/C,OAAO,CAAC,KAAK,CAAC,oCAAoC,MAAM,GAAG,EAAG,SAAmB,CAAC,OAAO,CAAC,CAAC;gBAC7F,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAE1B,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;YACtE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,OAAO,GAAG,4BAA4B,SAAS,EAAE,CAAC;QACxD,MAAM,eAAe,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE;YAC5E,EAAE,EAAE,SAAS;YACb,OAAO;YACP,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC;YAC9B,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC;SAC/B,CAAC,CAAC;QAEH,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAwB,EACxB,MAAoB,EACpB,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,KAAK,GAAG,MAAM,sBAAsB,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;IACtF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACrD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,CAAC,MAAM,UAAU,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,yBAAyB,CAAC,SAAS,EAAE,SAAS,UAAU,EAAE,CAAC,CAAC;QAE9E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QACxF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAErB,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,MAAM,cAAc,CAClB,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,cAAc,EACrB,IAAI,CAAC,OAAO,EACZ,GAAG,CAAC,WAAW,EAAE,CAClB,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACvB,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YAC/E,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK;YACzB,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE;YAC1B,CAAC,CAAC,MAAM,CAAC,OAAO;gBACd,CAAC,CAAC,YAAY,MAAM,CAAC,UAAU,GAAG;gBAClC,CAAC,CAAC,MAAM,CAAC,SAAS;oBAChB,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE;oBACxB,CAAC,CAAC,sBAAsB,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iFAAiF;AAEjF,MAAM,UAAU,eAAe,CAC7B,SAAwB,EACxB,MAAoB;IAEpB,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAExC,MAAM,GAAG,GAAG,GAAG,EAAE;QACf,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACxD,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CACT,yCAAyC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,sBAAsB,CAC7F,CAAC;IAEF,IAAI,WAAW,GAA0C,IAAI,CAAC;IAE9D,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;QACnC,GAAG,EAAE,CAAC;QACN,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACxC,IAAI,OAAO,WAAW,CAAC,KAAK,KAAK,UAAU;YAAE,WAAW,CAAC,KAAK,EAAE,CAAC;IACnE,CAAC,EAAE,KAAK,CAAC,CAAC;IAEV,IAAI,OAAO,YAAY,CAAC,KAAK,KAAK,UAAU;QAAE,YAAY,CAAC,KAAK,EAAE,CAAC;IAEnE,OAAO,GAAG,EAAE;QACV,YAAY,CAAC,YAAY,CAAC,CAAC;QAC3B,IAAI,WAAW;YAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * digestPreferences.ts
3
+ *
4
+ * CRUD for the `digest_preferences` Postgres table via Supabase PostgREST.
5
+ * Stores each user's digest opt-in state and last-sent timestamp.
6
+ *
7
+ * Table: scripts/digest-preferences.sql
8
+ */
9
+ export interface DigestPreference {
10
+ user_id: string;
11
+ email: string;
12
+ enabled: boolean;
13
+ last_sent_at: string | null;
14
+ created_at: string;
15
+ }
16
+ export declare function getDigestPreference(supabaseUrl: string, serviceRoleKey: string, userId: string): Promise<DigestPreference | null>;
17
+ export declare function listEnabledPreferences(supabaseUrl: string, serviceRoleKey: string): Promise<DigestPreference[]>;
18
+ /**
19
+ * Upsert a preference row. On insert, sets enabled=true and records email.
20
+ * On update, only changes the fields provided.
21
+ */
22
+ export declare function upsertDigestPreference(supabaseUrl: string, serviceRoleKey: string, pref: Pick<DigestPreference, "user_id" | "email"> & Partial<DigestPreference>): Promise<void>;
23
+ export declare function updateDigestEnabled(supabaseUrl: string, serviceRoleKey: string, userId: string, enabled: boolean): Promise<void>;
24
+ export declare function markDigestSent(supabaseUrl: string, serviceRoleKey: string, userId: string, sentAt: string): Promise<void>;
25
+ //# sourceMappingURL=digestPreferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digestPreferences.d.ts","sourceRoot":"","sources":["../../src/digest/digestPreferences.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAUD,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQlC;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAS7B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAC5E,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAaf"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * digestPreferences.ts
3
+ *
4
+ * CRUD for the `digest_preferences` Postgres table via Supabase PostgREST.
5
+ * Stores each user's digest opt-in state and last-sent timestamp.
6
+ *
7
+ * Table: scripts/digest-preferences.sql
8
+ */
9
+ function headers(serviceRoleKey) {
10
+ return {
11
+ "content-type": "application/json",
12
+ apikey: serviceRoleKey,
13
+ authorization: `Bearer ${serviceRoleKey}`,
14
+ };
15
+ }
16
+ export async function getDigestPreference(supabaseUrl, serviceRoleKey, userId) {
17
+ const url = `${supabaseUrl}/rest/v1/digest_preferences` +
18
+ `?user_id=eq.${encodeURIComponent(userId)}&limit=1`;
19
+ const res = await fetch(url, { headers: headers(serviceRoleKey) });
20
+ if (!res.ok)
21
+ return null;
22
+ const rows = (await res.json());
23
+ return rows[0] ?? null;
24
+ }
25
+ export async function listEnabledPreferences(supabaseUrl, serviceRoleKey) {
26
+ const url = `${supabaseUrl}/rest/v1/digest_preferences?enabled=eq.true&limit=2000`;
27
+ const res = await fetch(url, { headers: headers(serviceRoleKey) });
28
+ if (!res.ok) {
29
+ console.error(`[digest] list preferences failed (${res.status})`);
30
+ return [];
31
+ }
32
+ return (await res.json());
33
+ }
34
+ /**
35
+ * Upsert a preference row. On insert, sets enabled=true and records email.
36
+ * On update, only changes the fields provided.
37
+ */
38
+ export async function upsertDigestPreference(supabaseUrl, serviceRoleKey, pref) {
39
+ const url = `${supabaseUrl}/rest/v1/digest_preferences`;
40
+ const res = await fetch(url, {
41
+ method: "POST",
42
+ headers: {
43
+ ...headers(serviceRoleKey),
44
+ prefer: "resolution=merge-duplicates,return=minimal",
45
+ },
46
+ body: JSON.stringify({ enabled: true, ...pref }),
47
+ });
48
+ if (!res.ok) {
49
+ const body = await res.text().catch(() => "");
50
+ throw new Error(`upsertDigestPreference failed (${res.status}): ${body}`);
51
+ }
52
+ }
53
+ export async function updateDigestEnabled(supabaseUrl, serviceRoleKey, userId, enabled) {
54
+ const url = `${supabaseUrl}/rest/v1/digest_preferences` +
55
+ `?user_id=eq.${encodeURIComponent(userId)}`;
56
+ const res = await fetch(url, {
57
+ method: "PATCH",
58
+ headers: { ...headers(serviceRoleKey), prefer: "return=minimal" },
59
+ body: JSON.stringify({ enabled }),
60
+ });
61
+ if (!res.ok) {
62
+ const body = await res.text().catch(() => "");
63
+ throw new Error(`updateDigestEnabled failed (${res.status}): ${body}`);
64
+ }
65
+ }
66
+ export async function markDigestSent(supabaseUrl, serviceRoleKey, userId, sentAt) {
67
+ const url = `${supabaseUrl}/rest/v1/digest_preferences` +
68
+ `?user_id=eq.${encodeURIComponent(userId)}`;
69
+ const res = await fetch(url, {
70
+ method: "PATCH",
71
+ headers: { ...headers(serviceRoleKey), prefer: "return=minimal" },
72
+ body: JSON.stringify({ last_sent_at: sentAt }),
73
+ });
74
+ if (!res.ok) {
75
+ const body = await res.text().catch(() => "");
76
+ throw new Error(`markDigestSent failed (${res.status}): ${body}`);
77
+ }
78
+ }
79
+ //# sourceMappingURL=digestPreferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digestPreferences.js","sourceRoot":"","sources":["../../src/digest/digestPreferences.ts"],"names":[],"mappings":"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"}