@rubytech/create-sitedesk-code 0.1.519 → 0.1.520

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 (289) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +65 -0
  3. package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +155 -0
  4. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  5. package/payload/platform/plugins/cloudflare/bin/portal-assemble.mjs +187 -0
  6. package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +46 -5
  7. package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +19 -7
  8. package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +154 -38
  9. package/payload/platform/plugins/cloudflare/mcp/__tests__/file-routes.test.ts +100 -29
  10. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-assemble.test.ts +143 -0
  11. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +12 -5
  12. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +15 -3
  13. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +105 -11
  14. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +15 -2
  15. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +9 -5
  16. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +47 -5
  17. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-session-grant.test.ts +26 -2
  18. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +12 -4
  19. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +23 -15
  20. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +23 -15
  21. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +8 -0
  22. package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +23 -6
  23. package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +36 -20
  24. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +85 -22
  25. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +47 -7
  26. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/authorize.ts +27 -18
  27. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/ratelimit.ts +10 -3
  28. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +27 -11
  29. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +18 -9
  30. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +16 -0
  31. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +16 -4
  32. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +26 -11
  33. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +14 -10
  34. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +13 -9
  35. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +42 -22
  36. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +17 -4
  37. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  38. package/payload/platform/plugins/email/PLUGIN.md +20 -1
  39. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list-tool.test.d.ts +2 -0
  40. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list-tool.test.d.ts.map +1 -0
  41. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list-tool.test.js +142 -0
  42. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list-tool.test.js.map +1 -0
  43. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list.test.d.ts +2 -0
  44. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list.test.d.ts.map +1 -0
  45. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list.test.js +153 -0
  46. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-list.test.js.map +1 -0
  47. package/payload/platform/plugins/email/mcp/dist/__tests__/email-signature-set.test.d.ts +2 -0
  48. package/payload/platform/plugins/email/mcp/dist/__tests__/email-signature-set.test.d.ts.map +1 -0
  49. package/payload/platform/plugins/email/mcp/dist/__tests__/email-signature-set.test.js +203 -0
  50. package/payload/platform/plugins/email/mcp/dist/__tests__/email-signature-set.test.js.map +1 -0
  51. package/payload/platform/plugins/email/mcp/dist/index.js +69 -0
  52. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  53. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/compose.js +5 -7
  55. package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +42 -0
  57. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  58. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +76 -0
  59. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  60. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts +38 -0
  61. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts.map +1 -1
  62. package/payload/platform/plugins/email/mcp/dist/lib/signature.js +67 -2
  63. package/payload/platform/plugins/email/mcp/dist/lib/signature.js.map +1 -1
  64. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-list.d.ts +10 -0
  65. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-list.d.ts.map +1 -0
  66. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-list.js +73 -0
  67. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-list.js.map +1 -0
  68. package/payload/platform/plugins/email/mcp/dist/tools/email-signature-set.d.ts +18 -0
  69. package/payload/platform/plugins/email/mcp/dist/tools/email-signature-set.d.ts.map +1 -0
  70. package/payload/platform/plugins/email/mcp/dist/tools/email-signature-set.js +38 -0
  71. package/payload/platform/plugins/email/mcp/dist/tools/email-signature-set.js.map +1 -0
  72. package/payload/platform/plugins/email/references/email-reference.md +14 -2
  73. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +10 -0
  74. package/payload/platform/plugins/email/skills/email-signature/SKILL.md +61 -0
  75. package/payload/platform/plugins/scheduling/PLUGIN.md +8 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/index.js +26 -15
  77. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.d.ts +2 -0
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.d.ts.map +1 -0
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js +48 -0
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js.map +1 -0
  82. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +24 -0
  83. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -0
  84. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +33 -0
  85. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -0
  86. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +22 -1
  87. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  88. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel-meeting.test.d.ts +2 -0
  89. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel-meeting.test.d.ts.map +1 -0
  90. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel-meeting.test.js +30 -0
  91. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel-meeting.test.js.map +1 -0
  92. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-meeting.test.d.ts +2 -0
  93. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-meeting.test.d.ts.map +1 -0
  94. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-meeting.test.js +43 -0
  95. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-meeting.test.js.map +1 -0
  96. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.d.ts +2 -0
  97. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.d.ts.map +1 -0
  98. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js +87 -0
  99. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list.test.js.map +1 -0
  100. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-meeting.test.d.ts +2 -0
  101. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-meeting.test.d.ts.map +1 -0
  102. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-meeting.test.js +48 -0
  103. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-meeting.test.js.map +1 -0
  104. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -1
  105. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +14 -1
  106. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -1
  107. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +4 -1
  108. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
  109. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +39 -2
  110. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
  111. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +4 -1
  112. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -1
  113. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +50 -1
  114. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -1
  115. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  116. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +43 -1
  117. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  118. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  119. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  120. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  121. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  122. package/payload/server/public/activity.html +5 -5
  123. package/payload/server/public/agents.html +4 -4
  124. package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js +1 -0
  125. package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.br +0 -0
  126. package/payload/server/public/assets/AdminLoginScreens-SW7mEMcP.js.gz +0 -0
  127. package/payload/server/public/assets/{AdminShell-Bpo_wgxm.js → AdminShell-Djg6ndne.js} +1 -1
  128. package/payload/server/public/assets/AdminShell-Djg6ndne.js.br +0 -0
  129. package/payload/server/public/assets/AdminShell-Djg6ndne.js.gz +0 -0
  130. package/payload/server/public/assets/{activity-DOsfKzT3.js → activity-sqbyX0pX.js} +1 -1
  131. package/payload/server/public/assets/activity-sqbyX0pX.js.br +0 -0
  132. package/payload/server/public/assets/activity-sqbyX0pX.js.gz +0 -0
  133. package/payload/server/public/assets/admin-D9Bt47GY.js +1 -0
  134. package/payload/server/public/assets/admin-D9Bt47GY.js.br +0 -0
  135. package/payload/server/public/assets/admin-D9Bt47GY.js.gz +0 -0
  136. package/payload/server/public/assets/{agents-DqDnZTH_.js → agents-BFYM5EDA.js} +1 -1
  137. package/payload/server/public/assets/agents-BFYM5EDA.js.br +0 -0
  138. package/payload/server/public/assets/agents-BFYM5EDA.js.gz +0 -0
  139. package/payload/server/public/assets/{browser-FL17Dfp0.js → browser-DP5GZe4G.js} +1 -1
  140. package/payload/server/public/assets/browser-DP5GZe4G.js.br +0 -0
  141. package/payload/server/public/assets/browser-DP5GZe4G.js.gz +0 -0
  142. package/payload/server/public/assets/{calendar-CLfLvsnV.js → calendar-BKaemLDG.js} +1 -1
  143. package/payload/server/public/assets/calendar-BKaemLDG.js.br +0 -0
  144. package/payload/server/public/assets/calendar-BKaemLDG.js.gz +0 -0
  145. package/payload/server/public/assets/chat-DPhT6urD.js +1 -0
  146. package/payload/server/public/assets/chat-DPhT6urD.js.br +1 -0
  147. package/payload/server/public/assets/chat-DPhT6urD.js.gz +0 -0
  148. package/payload/server/public/assets/chevron-left-BSdXvJcH.js +1 -0
  149. package/payload/server/public/assets/chevron-right-DI7vI3IB.js +1 -0
  150. package/payload/server/public/assets/chevron-right-DI7vI3IB.js.br +0 -0
  151. package/payload/server/public/assets/clock-YUTPXZH5.js +1 -0
  152. package/payload/server/public/assets/clock-YUTPXZH5.js.br +0 -0
  153. package/payload/server/public/assets/clock-YUTPXZH5.js.gz +0 -0
  154. package/payload/server/public/assets/data-CfjttTvR.js +1 -0
  155. package/payload/server/public/assets/data-CfjttTvR.js.br +2 -0
  156. package/payload/server/public/assets/data-CfjttTvR.js.gz +0 -0
  157. package/payload/server/public/assets/{file-text-DNm0AzUz.js → file-text-BKK7R-k_.js} +1 -1
  158. package/payload/server/public/assets/file-text-BKK7R-k_.js.br +0 -0
  159. package/payload/server/public/assets/file-text-BKK7R-k_.js.gz +0 -0
  160. package/payload/server/public/assets/{graph-fAvmlVzh.js → graph-FuxYySjK.js} +1 -1
  161. package/payload/server/public/assets/graph-FuxYySjK.js.br +0 -0
  162. package/payload/server/public/assets/graph-FuxYySjK.js.gz +0 -0
  163. package/payload/server/public/assets/{graph-labels-CwdkCxfs.js → graph-labels-Cip6fmKK.js} +1 -1
  164. package/payload/server/public/assets/graph-labels-Cip6fmKK.js.br +0 -0
  165. package/payload/server/public/assets/graph-labels-Cip6fmKK.js.gz +0 -0
  166. package/payload/server/public/assets/{maximize-2-B6tAPhC2.js → maximize-2-D79AhVDo.js} +1 -1
  167. package/payload/server/public/assets/maximize-2-D79AhVDo.js.br +0 -0
  168. package/payload/server/public/assets/maximize-2-D79AhVDo.js.gz +0 -0
  169. package/payload/server/public/assets/{operator-CTKvBVjk.js → operator-BPAfgFhP.js} +1 -1
  170. package/payload/server/public/assets/operator-BPAfgFhP.js.br +0 -0
  171. package/payload/server/public/assets/operator-BPAfgFhP.js.gz +0 -0
  172. package/payload/server/public/assets/{page-BWoNW3sa.js → page--78CbdFs.js} +1 -1
  173. package/payload/server/public/assets/page--78CbdFs.js.br +0 -0
  174. package/payload/server/public/assets/page--78CbdFs.js.gz +0 -0
  175. package/payload/server/public/assets/{page-O5HkHQw4.js → page-BqEyS0W4.js} +1 -1
  176. package/payload/server/public/assets/page-BqEyS0W4.js.br +0 -0
  177. package/payload/server/public/assets/page-BqEyS0W4.js.gz +0 -0
  178. package/payload/server/public/assets/{public-BF1rnVPa.js → public-D1xs3FzD.js} +1 -1
  179. package/payload/server/public/assets/public-D1xs3FzD.js.br +0 -0
  180. package/payload/server/public/assets/public-D1xs3FzD.js.gz +0 -0
  181. package/payload/server/public/assets/{rotate-ccw-B8TqfCy4.js → rotate-ccw-CcU_XD8B.js} +1 -1
  182. package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.br +0 -0
  183. package/payload/server/public/assets/rotate-ccw-CcU_XD8B.js.gz +0 -0
  184. package/payload/server/public/assets/{routines-4R7BLZyH.js → routines-CA7D0myr.js} +1 -1
  185. package/payload/server/public/assets/routines-CA7D0myr.js.br +0 -0
  186. package/payload/server/public/assets/routines-CA7D0myr.js.gz +0 -0
  187. package/payload/server/public/assets/{skills-CQ9Qs2tg.js → skills-CfVPPSxO.js} +1 -1
  188. package/payload/server/public/assets/skills-CfVPPSxO.js.br +0 -0
  189. package/payload/server/public/assets/skills-CfVPPSxO.js.gz +0 -0
  190. package/payload/server/public/assets/{tasks-DePyOrRk.js → tasks-DgieyZqz.js} +1 -1
  191. package/payload/server/public/assets/tasks-DgieyZqz.js.br +0 -0
  192. package/payload/server/public/assets/tasks-DgieyZqz.js.gz +0 -0
  193. package/payload/server/public/assets/{time-entry-format-D9gtnG01.js → time-entry-format-IFqinQJj.js} +1 -1
  194. package/payload/server/public/assets/time-entry-format-IFqinQJj.js.br +0 -0
  195. package/payload/server/public/assets/time-entry-format-IFqinQJj.js.gz +0 -0
  196. package/payload/server/public/assets/{triangle-alert-DAtm2mLL.js → triangle-alert-C5vH__sq.js} +1 -1
  197. package/payload/server/public/assets/triangle-alert-C5vH__sq.js.br +0 -0
  198. package/payload/server/public/assets/triangle-alert-C5vH__sq.js.gz +0 -0
  199. package/payload/server/public/assets/{useCopyFeedback-uMJT-p6i.js → useCopyFeedback-BeL2gPl6.js} +1 -1
  200. package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.br +0 -0
  201. package/payload/server/public/assets/useCopyFeedback-BeL2gPl6.js.gz +0 -0
  202. package/payload/server/public/assets/{useSubAccountSwitcher-iCzsV96x.css → useSubAccountSwitcher-DLqUQzOl.css} +1 -1
  203. package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.br +0 -0
  204. package/payload/server/public/assets/useSubAccountSwitcher-DLqUQzOl.css.gz +0 -0
  205. package/payload/server/public/assets/{useVoiceRecorder-BFrPC-s7.js → useVoiceRecorder-Cpw3aOSt.js} +1 -1
  206. package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.br +0 -0
  207. package/payload/server/public/assets/useVoiceRecorder-Cpw3aOSt.js.gz +0 -0
  208. package/payload/server/public/assets/{wrench-CYgaN7K9.js → wrench-BBEup3hd.js} +1 -1
  209. package/payload/server/public/assets/wrench-BBEup3hd.js.br +0 -0
  210. package/payload/server/public/assets/wrench-BBEup3hd.js.gz +0 -0
  211. package/payload/server/public/browser.html +4 -4
  212. package/payload/server/public/calendar.html +7 -7
  213. package/payload/server/public/chat.html +13 -13
  214. package/payload/server/public/data.html +11 -11
  215. package/payload/server/public/graph.html +9 -9
  216. package/payload/server/public/index.html +14 -14
  217. package/payload/server/public/operator.html +14 -14
  218. package/payload/server/public/public.html +13 -13
  219. package/payload/server/public/routines.html +6 -6
  220. package/payload/server/public/skills.html +5 -5
  221. package/payload/server/public/tasks.html +6 -6
  222. package/payload/server/server.js +454 -352
  223. package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js +0 -1
  224. package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.br +0 -0
  225. package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.gz +0 -0
  226. package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.br +0 -0
  227. package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.gz +0 -0
  228. package/payload/server/public/assets/activity-DOsfKzT3.js.br +0 -0
  229. package/payload/server/public/assets/activity-DOsfKzT3.js.gz +0 -0
  230. package/payload/server/public/assets/admin-COgD1z5a.js +0 -1
  231. package/payload/server/public/assets/admin-COgD1z5a.js.br +0 -0
  232. package/payload/server/public/assets/admin-COgD1z5a.js.gz +0 -0
  233. package/payload/server/public/assets/agents-DqDnZTH_.js.br +0 -0
  234. package/payload/server/public/assets/agents-DqDnZTH_.js.gz +0 -0
  235. package/payload/server/public/assets/browser-FL17Dfp0.js.br +0 -0
  236. package/payload/server/public/assets/browser-FL17Dfp0.js.gz +0 -0
  237. package/payload/server/public/assets/calendar-CLfLvsnV.js.br +0 -0
  238. package/payload/server/public/assets/calendar-CLfLvsnV.js.gz +0 -0
  239. package/payload/server/public/assets/chat-DYq4FxMG.js +0 -1
  240. package/payload/server/public/assets/chat-DYq4FxMG.js.br +0 -0
  241. package/payload/server/public/assets/chat-DYq4FxMG.js.gz +0 -0
  242. package/payload/server/public/assets/chevron-left-DWt5cdUo.js +0 -1
  243. package/payload/server/public/assets/chevron-right-B9suVrYy.js +0 -1
  244. package/payload/server/public/assets/chevron-right-B9suVrYy.js.br +0 -0
  245. package/payload/server/public/assets/clock-DW6qVXtv.js +0 -1
  246. package/payload/server/public/assets/clock-DW6qVXtv.js.br +0 -0
  247. package/payload/server/public/assets/clock-DW6qVXtv.js.gz +0 -0
  248. package/payload/server/public/assets/data-C8rbOgds.js +0 -1
  249. package/payload/server/public/assets/data-C8rbOgds.js.br +0 -0
  250. package/payload/server/public/assets/data-C8rbOgds.js.gz +0 -0
  251. package/payload/server/public/assets/file-text-DNm0AzUz.js.br +0 -0
  252. package/payload/server/public/assets/file-text-DNm0AzUz.js.gz +0 -0
  253. package/payload/server/public/assets/graph-fAvmlVzh.js.br +0 -0
  254. package/payload/server/public/assets/graph-fAvmlVzh.js.gz +0 -0
  255. package/payload/server/public/assets/graph-labels-CwdkCxfs.js.br +0 -0
  256. package/payload/server/public/assets/graph-labels-CwdkCxfs.js.gz +0 -0
  257. package/payload/server/public/assets/maximize-2-B6tAPhC2.js.br +0 -0
  258. package/payload/server/public/assets/maximize-2-B6tAPhC2.js.gz +0 -0
  259. package/payload/server/public/assets/operator-CTKvBVjk.js.br +0 -0
  260. package/payload/server/public/assets/operator-CTKvBVjk.js.gz +0 -0
  261. package/payload/server/public/assets/page-BWoNW3sa.js.br +0 -0
  262. package/payload/server/public/assets/page-BWoNW3sa.js.gz +0 -0
  263. package/payload/server/public/assets/page-O5HkHQw4.js.br +0 -0
  264. package/payload/server/public/assets/page-O5HkHQw4.js.gz +0 -0
  265. package/payload/server/public/assets/public-BF1rnVPa.js.br +0 -0
  266. package/payload/server/public/assets/public-BF1rnVPa.js.gz +0 -0
  267. package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.br +0 -0
  268. package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.gz +0 -0
  269. package/payload/server/public/assets/routines-4R7BLZyH.js.br +0 -0
  270. package/payload/server/public/assets/routines-4R7BLZyH.js.gz +0 -0
  271. package/payload/server/public/assets/skills-CQ9Qs2tg.js.br +0 -0
  272. package/payload/server/public/assets/skills-CQ9Qs2tg.js.gz +0 -0
  273. package/payload/server/public/assets/tasks-DePyOrRk.js.br +0 -0
  274. package/payload/server/public/assets/tasks-DePyOrRk.js.gz +0 -0
  275. package/payload/server/public/assets/time-entry-format-D9gtnG01.js.br +0 -0
  276. package/payload/server/public/assets/time-entry-format-D9gtnG01.js.gz +0 -0
  277. package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.br +0 -0
  278. package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.gz +0 -0
  279. package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.br +0 -0
  280. package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.gz +0 -0
  281. package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.br +0 -0
  282. package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.gz +0 -0
  283. package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.br +0 -0
  284. package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.gz +0 -0
  285. package/payload/server/public/assets/wrench-CYgaN7K9.js.br +0 -1
  286. package/payload/server/public/assets/wrench-CYgaN7K9.js.gz +0 -0
  287. /package/payload/server/public/assets/{useSubAccountSwitcher-BufCPDDH.js → useSubAccountSwitcher-CH9qm2Ad.js} +0 -0
  288. /package/payload/server/public/assets/{useSubAccountSwitcher-BufCPDDH.js.br → useSubAccountSwitcher-CH9qm2Ad.js.br} +0 -0
  289. /package/payload/server/public/assets/{useSubAccountSwitcher-BufCPDDH.js.gz → useSubAccountSwitcher-CH9qm2Ad.js.gz} +0 -0
@@ -48,7 +48,7 @@ export async function processUpload(
48
48
  // printed a hardcoded ms=0, which an operator reads as "took no time" rather
49
49
  // than "was never measured".
50
50
  const startedMs = now()
51
- const session = await resolveSession(env.DB, sessionId, startedMs)
51
+ const session = await resolveSession(env.DB, sessionId, env.PORTAL_ACCOUNT_ID, startedMs)
52
52
  if (!session) {
53
53
  log('[data-portal] op=upload owner=none result=denied')
54
54
  return { status: 401, payload: { ok: false, error: 'denied' } }
@@ -63,7 +63,7 @@ export async function processUpload(
63
63
 
64
64
  // The target is resolved BEFORE any R2 call, the same ordering rule the owner
65
65
  // gate follows: a denied caller must never be able to cause a store write.
66
- const target = await checkTarget(env.DB, session.accountId, session.folders, targetPath)
66
+ const target = await checkTarget(env.DB, env.PORTAL_ACCOUNT_ID, session.folders, targetPath)
67
67
  if (!target.ok) {
68
68
  log(
69
69
  `[data-portal] op=target-check uploadId=${uploadId} owner=${q(owner)} ` +
@@ -76,10 +76,11 @@ export async function processUpload(
76
76
  `targetPath=${q(targetPath)} result=allowed`,
77
77
  )
78
78
 
79
- const key = uploadKey(owner, targetPath, filename)
80
- // The key is derived from the session owner, then re-checked through the same
81
- // gate the other routes use — a filename carrying '..' must not escape.
82
- if (!authorizeKey(owner, key)) {
79
+ const key = uploadKey(env.PORTAL_ACCOUNT_ID, owner, targetPath, filename)
80
+ // The key is derived from this portal's account and the session owner, then
81
+ // re-checked through the same gate the other routes use — a filename carrying
82
+ // '..' must not escape.
83
+ if (!authorizeKey(env.PORTAL_ACCOUNT_ID, owner, key)) {
83
84
  log(`[data-portal] op=request uploadId=${uploadId} owner=${q(owner)} result=bad-filename`)
84
85
  return { status: 400, payload: { ok: false, error: 'invalid filename' } }
85
86
  }
@@ -143,8 +144,9 @@ export async function processUpload(
143
144
  // request time, which is why this line exists.
144
145
  const objectPresent = (await env.BUCKET.head(key)) !== null
145
146
  const rowPresent =
146
- (await env.DB.prepare('SELECT fileId FROM manifest WHERE objectKey = ?').bind(key).first()) !==
147
- null
147
+ (await env.DB.prepare('SELECT fileId FROM manifest WHERE objectKey = ? AND accountId = ?')
148
+ .bind(key, env.PORTAL_ACCOUNT_ID)
149
+ .first()) !== null
148
150
  log(
149
151
  `[data-portal] op=complete uploadId=${uploadId} ms=${now() - startedMs} objectPresent=${objectPresent} rowPresent=${rowPresent}`,
150
152
  )
@@ -158,10 +160,14 @@ async function writeManifestRow(
158
160
  env: PortalEnv,
159
161
  row: { fileId: string; owner: string; filename: string; key: string; size: number; at: string; targetPath: string },
160
162
  ): Promise<void> {
163
+ // The row carries its account and the upsert conflicts on (accountId,
164
+ // objectKey). Conflicting on objectKey alone would be a takeover on the
165
+ // shared store: another client's upload at the same key would seize the
166
+ // existing row, reset its ingested flag, and redirect the file.
161
167
  await env.DB.prepare(
162
- `INSERT INTO manifest (fileId, ownerId, filename, objectKey, size, uploadedAt, ingested, targetPath)
163
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
164
- ON CONFLICT (objectKey) DO UPDATE SET
168
+ `INSERT INTO manifest (fileId, accountId, ownerId, filename, objectKey, size, uploadedAt, ingested, targetPath)
169
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
170
+ ON CONFLICT (accountId, objectKey) DO UPDATE SET
165
171
  fileId = excluded.fileId,
166
172
  size = excluded.size,
167
173
  uploadedAt = excluded.uploadedAt,
@@ -170,7 +176,17 @@ async function writeManifestRow(
170
176
  devicePath = '',
171
177
  deleted = 0`,
172
178
  )
173
- .bind(row.fileId, row.owner, row.filename, row.key, row.size, row.at, 0, row.targetPath)
179
+ .bind(
180
+ row.fileId,
181
+ env.PORTAL_ACCOUNT_ID,
182
+ row.owner,
183
+ row.filename,
184
+ row.key,
185
+ row.size,
186
+ row.at,
187
+ 0,
188
+ row.targetPath,
189
+ )
174
190
  .run()
175
191
  }
176
192
 
@@ -192,7 +208,7 @@ export async function processMultipartCreate(
192
208
  now: () => number,
193
209
  ): Promise<Handler> {
194
210
  const startedMs = now()
195
- const session = await resolveSession(env.DB, sessionId, startedMs)
211
+ const session = await resolveSession(env.DB, sessionId, env.PORTAL_ACCOUNT_ID, startedMs)
196
212
  if (!session) {
197
213
  log('[data-portal] op=mp-create owner=none result=denied')
198
214
  return { status: 401, payload: { ok: false, error: 'denied' } }
@@ -215,7 +231,7 @@ export async function processMultipartCreate(
215
231
  }
216
232
  }
217
233
 
218
- const target = await checkTarget(env.DB, session.accountId, session.folders, targetPath)
234
+ const target = await checkTarget(env.DB, env.PORTAL_ACCOUNT_ID, session.folders, targetPath)
219
235
  if (!target.ok) {
220
236
  log(
221
237
  `[data-portal] op=target-check uploadId=${uploadId} owner=${q(owner)} ` +
@@ -224,8 +240,8 @@ export async function processMultipartCreate(
224
240
  return { status: 403, payload: { ok: false, error: 'target not allowed' } }
225
241
  }
226
242
 
227
- const key = uploadKey(owner, targetPath, filename)
228
- if (!authorizeKey(owner, key)) {
243
+ const key = uploadKey(env.PORTAL_ACCOUNT_ID, owner, targetPath, filename)
244
+ if (!authorizeKey(env.PORTAL_ACCOUNT_ID, owner, key)) {
229
245
  log(`[data-portal] op=mp-create uploadId=${uploadId} owner=${q(owner)} result=bad-filename`)
230
246
  return { status: 400, payload: { ok: false, error: 'invalid filename' } }
231
247
  }
@@ -250,12 +266,15 @@ export async function processMultipartPart(
250
266
  env: PortalEnv,
251
267
  log: Logger,
252
268
  ): Promise<Handler> {
253
- const session = await resolveSession(env.DB, sessionId, Date.now())
269
+ const session = await resolveSession(env.DB, sessionId, env.PORTAL_ACCOUNT_ID, Date.now())
254
270
  if (!session) {
255
271
  log('[data-portal] op=r2-put owner=none result=denied')
256
272
  return { status: 401, payload: { ok: false, error: 'denied' } }
257
273
  }
258
- if (!authorizeKey(session.ownerId, key)) {
274
+ // The part key round-trips through the client on every call, so the full
275
+ // account and owner prefix is re-checked here rather than trusted from the
276
+ // create step that minted it.
277
+ if (!authorizeKey(env.PORTAL_ACCOUNT_ID, session.ownerId, key)) {
259
278
  log(`[data-portal] op=r2-put owner=${q(session.ownerId)} result=denied reason=not-owner`)
260
279
  return { status: 403, payload: { ok: false, error: 'denied' } }
261
280
  }
@@ -311,14 +330,14 @@ export async function processMultipartComplete(
311
330
  now: () => number,
312
331
  ): Promise<Handler> {
313
332
  const startedMs = now()
314
- const session = await resolveSession(env.DB, sessionId, startedMs)
333
+ const session = await resolveSession(env.DB, sessionId, env.PORTAL_ACCOUNT_ID, startedMs)
315
334
  if (!session) {
316
335
  log('[data-portal] op=mp-complete owner=none result=denied')
317
336
  return { status: 401, payload: { ok: false, error: 'denied' } }
318
337
  }
319
338
  const owner = session.ownerId
320
339
  const uploadId = newId()
321
- if (!authorizeKey(owner, key)) {
340
+ if (!authorizeKey(env.PORTAL_ACCOUNT_ID, owner, key)) {
322
341
  log(`[data-portal] op=mp-complete uploadId=${uploadId} owner=${q(owner)} result=denied reason=not-owner`)
323
342
  return { status: 403, payload: { ok: false, error: 'denied' } }
324
343
  }
@@ -366,8 +385,9 @@ export async function processMultipartComplete(
366
385
 
367
386
  const objectPresent = (await env.BUCKET.head(key)) !== null
368
387
  const rowPresent =
369
- (await env.DB.prepare('SELECT fileId FROM manifest WHERE objectKey = ?').bind(key).first()) !==
370
- null
388
+ (await env.DB.prepare('SELECT fileId FROM manifest WHERE objectKey = ? AND accountId = ?')
389
+ .bind(key, env.PORTAL_ACCOUNT_ID)
390
+ .first()) !== null
371
391
  log(
372
392
  `[data-portal] op=complete uploadId=${uploadId} ms=${now() - startedMs} ` +
373
393
  `objectPresent=${objectPresent} rowPresent=${rowPresent}`,
@@ -1,10 +1,18 @@
1
1
  # Cloudflare Pages config for the data portal (data-portal skill).
2
- # The skill fills the placeholders at assemble time:
2
+ #
3
+ # `bin/portal-assemble.mjs` fills these, and nothing else may: hand-filling them
4
+ # is what shipped a portal whose bucket name and config disagreed (Task 2099).
3
5
  # __PROJECT_NAME__ the Pages project name
4
- # __D1_DATABASE_NAME__ the D1 database name (<accountId>-<purpose>)
5
- # __D1_DATABASE_ID__ the id printed by `wrangler d1 create`
6
- # __R2_BUCKET_NAME__ the R2 bucket name (<accountId>-<purpose>)
6
+ # __D1_DATABASE_NAME__ the ONE shared house database (DATA_PORTAL_DB_NAME)
7
+ # __D1_DATABASE_ID__ its id (DATA_PORTAL_DB_ID)
8
+ # __R2_BUCKET_NAME__ the ONE shared house bucket (DATA_PORTAL_BUCKET)
9
+ # __PORTAL_ACCOUNT_ID__ this portal's own account id
7
10
  # __PORTAL_INSTALL_ORIGIN__ the install's PUBLIC origin (https://<public-tunnel-hostname>), NOT the admin/operator host
11
+ #
12
+ # The first three come from platform/config/cloudflare-house.env and are
13
+ # IDENTICAL in every portal: one store backs all of them. __PORTAL_ACCOUNT_ID__
14
+ # is what differs, and it is the only thing separating this client's rows and
15
+ # objects from another client's.
8
16
  name = "__PROJECT_NAME__"
9
17
  pages_build_output_dir = "."
10
18
  compatibility_date = "2024-01-01"
@@ -31,4 +39,9 @@ bucket_name = "__R2_BUCKET_NAME__"
31
39
  # directory it is handed, so a secret in it is a secret in a repo and one deploy
32
40
  # from being served. Set it with `wrangler pages secret put`.
33
41
  [vars]
42
+ # The tenant scope for every query and every object key. A var, not a secret:
43
+ # it is this client's own account id, not a credential, and the worker needs it
44
+ # on every request. Its partner PORTAL_FETCH_SECRET stays out of this file for
45
+ # the reason given above.
46
+ PORTAL_ACCOUNT_ID = "__PORTAL_ACCOUNT_ID__"
34
47
  PORTAL_INSTALL_ORIGIN = "__PORTAL_INSTALL_ORIGIN__"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email",
3
- "description": "Email — one or more dedicated email mailboxes per account, each addressable by a `mailbox` selector. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox previews (with best-effort per-archive section hints from the graph); email-fetch-body for the COMPLETE decoded body of one message (by uid or message_id) when a preview is not enough; email-fetch-attachment for the extracted TEXT of one attachment (PDF via pdftotext, text/* by decode) when data sits inside a file rather than the body. Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review; pass messageId for a threaded draft-reply), email-draft-edit (revise a stored draft, located by its Drafts UID or, when Gmail has renumbered it, its draft Message-ID), email-draft-send (dispatch a stored draft by Drafts UID, then remove it). Deleting: email-delete (move named UIDs from email-read/email-search to the mailbox Trash — recoverable, never an expunge). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract.",
3
+ "description": "Email — one or more dedicated email mailboxes per account, each addressable by a `mailbox` selector. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox previews (with best-effort per-archive section hints from the graph); email-fetch-body for the COMPLETE decoded body of one message (by uid or message_id) when a preview is not enough; email-fetch-attachment for the extracted TEXT of one attachment (PDF via pdftotext, text/* by decode) when data sits inside a file rather than the body. Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review; pass messageId for a threaded draft-reply), email-draft-edit (revise a stored draft, located by its Drafts UID or, when Gmail has renumbered it, its draft Message-ID), email-draft-send (dispatch a stored draft by Drafts UID, then remove it), email-draft-list (read the Drafts folder back and confirm what is actually in it — the only tool that can verify a draft's presence). Deleting: email-delete (move named UIDs from email-read/email-search to the mailbox Trash — recoverable, never an expunge). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-signature-set (install the signature every email-draft then appends automatically — never hand-write a signature into a draft body), email-status, email-otp-extract.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: email
3
- description: "Email — one or more dedicated email mailboxes per account, each addressable by a `mailbox` selector. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox previews (with best-effort per-archive section hints from the graph); email-fetch-body for the COMPLETE decoded body of one message (by uid or message_id) when a preview is not enough; email-fetch-attachment for the extracted TEXT of one attachment (PDF via pdftotext, text/* by decode) when data sits inside a file rather than the body. Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review; pass messageId for a threaded draft-reply), email-draft-edit (revise a stored draft, located by its Drafts UID or, when Gmail has renumbered it, its draft Message-ID), email-draft-send (dispatch a stored draft by Drafts UID, then remove it). Deleting: email-delete (move named UIDs from email-read/email-search to the mailbox Trash — recoverable, never an expunge). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-status, email-otp-extract."
3
+ description: "Email — one or more dedicated email mailboxes per account, each addressable by a `mailbox` selector. Retrieval: email-graph-query for stored email-thread history as :ConversationArchive {source:'email'} with :Section chunks (recall, search, 'threads about X', 'what did Y send?'); email-read / email-search for live IMAP inbox previews (with best-effort per-archive section hints from the graph); email-fetch-body for the COMPLETE decoded body of one message (by uid or message_id) when a preview is not enough; email-fetch-attachment for the extracted TEXT of one attachment (PDF via pdftotext, text/* by decode) when data sits inside a file rather than the body. Ingestion (operator-initiated, human-in-the-loop): email-fetch lists new messages, email-ingest writes the operator-approved subset via the conversation-archive pipeline. Sending: email-send (new), email-reply (threaded — resolves the original via IMAP Message-ID search), email-draft (compose to the mailbox Drafts folder, unsent, for operator review; pass messageId for a threaded draft-reply), email-draft-edit (revise a stored draft, located by its Drafts UID or, when Gmail has renumbered it, its draft Message-ID), email-draft-send (dispatch a stored draft by Drafts UID, then remove it), email-draft-list (read the Drafts folder back and confirm what is actually in it — the only tool that can verify a draft's presence). Deleting: email-delete (move named UIDs from email-read/email-search to the mailbox Trash — recoverable, never an expunge). Config: email-provider-info (pre-password host + app-password lookup from an address alone), email-setup, email-signature-set (install the signature every email-draft then appends automatically — never hand-write a signature into a draft body), email-status, email-otp-extract."
4
4
  tools:
5
5
  - name: email-provider-info
6
6
  publicAllowlist: false
@@ -10,6 +10,10 @@ tools:
10
10
  publicAllowlist: false
11
11
  adminAllowlist: false
12
12
  riskClass: write_local
13
+ - name: email-signature-set
14
+ publicAllowlist: false
15
+ adminAllowlist: false
16
+ riskClass: write_local
13
17
  - name: email-read
14
18
  publicAllowlist: false
15
19
  adminAllowlist: false
@@ -42,6 +46,13 @@ tools:
42
46
  publicAllowlist: false
43
47
  adminAllowlist: false
44
48
  riskClass: external
49
+ # adminAllowlist is true here and false on every sibling: this tool exists to
50
+ # be run AGAINST the other email tools' claims, so it is useless if only their
51
+ # callers can reach it. It is a pure read and writes nothing.
52
+ - name: email-draft-list
53
+ publicAllowlist: false
54
+ adminAllowlist: true
55
+ riskClass: read
45
56
  - name: email-search
46
57
  publicAllowlist: false
47
58
  adminAllowlist: false
@@ -101,6 +112,7 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
101
112
 
102
113
  - **Provider lookup:** `email-provider-info` — given just an email address, returns the inferred IMAP/SMTP hosts and the provider's app-password / trusted-application / IMAP-enable requirement, deterministically from the provider map. Call this BEFORE soliciting a password so the requirement is relayed first; most providers reject the normal login password. Returns "unknown" (operator supplies hosts) or "unsupported" (provider blocks IMAP/SMTP, e.g. Tuta).
103
114
  - **Setup:** `email-setup` — collect credentials in plain chat and connect IMAP/SMTP. Hosts are auto-inferred for known providers; supports alias addresses (`agentAddress`). For hosts where the client-visible Drafts folder is not the one flagged `\Drafts`, pass `draftsFolder` to name it explicitly (e.g. `INBOX.Drafts`).
115
+ - **Signature:** `email-signature-set` — install the account's signature into the three files `email-draft` already reads at the account root (`email-signature.txt`, `.html`, `.font`). Plain text and HTML are stored and appended byte-for-byte; `font` is one CSS font-family value that wraps the HTML body and has no plain-text effect. **A part you omit is removed**, so a text-only call clears a stale HTML signature rather than leaving it to keep being appended; pass every part you want to keep on each call. A call with no parts at all is refused rather than treated as "erase everything". Because the append is deterministic and happens on every draft, a signature must never be written into a draft body by hand — that duplicates the stored one and drifts from it. Pass the operator's real signature read from its source of truth; reconstructing markup from memory or from stripped graph text is what put a dead logo URL into a client draft. Account-scoped, no `mailbox` selector: one signature per account.
104
116
  - **Read inbox:** `email-read` — metadata only (UID, sender, subject, date), plus a best-effort `Archive`/`Archive-Section` hint when the IMAP message's `receivedAt` falls inside a stored `:ConversationArchive {source:'email'}` section, plus an `Attachments (N): filename (mime, size); …` footer when the message carries enclosures. Supports pagination (`before_uid`), folders (`inbox`/`sent`), filtering by sender/date/subject. Metadata is derived from IMAP `bodyStructure` — no bytes downloaded.
105
117
  - **Search inbox:** `email-search` — live IMAP search by sender, subject, body keyword, date range. Same per-archive hint and attachment footer as `email-read`.
106
118
  - **Delete (move to Trash):** `email-delete` — move the messages named by `uids` (from a prior `email-read`/`email-search`, scoped to a source `folder` of `inbox` or `sent`) into the mailbox's Trash folder. **Recoverable, never an expunge** — the message leaves the inbox and permanent removal is left to the operator's own mail client or the provider's Trash auto-purge; this tool never permanently deletes. Trash is resolved per provider: the `\Trash` special-use folder if advertised, else the first existing of `Trash`, `[Gmail]/Trash`, `Deleted Items`, `Deleted Messages`. `Junk` is never treated as Trash. A mailbox with no resolvable Trash yields a typed error and moves nothing — there is no silent expunge fallback. A `uid` that is already gone is counted not-moved without failing the rest; the result reports `Moved N of M message(s) to <Trash>`. It deletes only the UIDs you name, never by search criteria.
@@ -110,6 +122,12 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
110
122
  - **Reply:** `email-reply` — threaded reply to an existing email by `messageId`. Resolves the original envelope via IMAP `SEARCH HEADER Message-ID` (INBOX then `\Sent`), so threading works for any message still present on the server even if the graph never ingested it. Supports `replyAll`, `cc`/`bcc`, and `attachments` (same path rules as `email-send`). Explicit `cc`/`bcc` are added in addition to whatever `replyAll` places in `To`, passed through as given (no overlap de-duplication against `To`).
111
123
  - **Compose to Drafts:** `email-draft` — builds the same MIME message as `email-send` (same `cc`/`bcc`/`attachments` rules; unlike a dispatched send, the stored draft keeps its `Bcc:` header, because the draft bytes are the only recipient carrier) but APPENDs it to the mailbox's `\Drafts` special-use folder with the `\Draft` flag instead of dispatching it. Nothing is sent. Pass `messageId` to compose a threaded draft-reply: the parent is resolved via IMAP and the draft carries `In-Reply-To`/`References` plus a `Re:` subject from the parent, exactly like `email-reply` (the `subject` argument is then optional and ignored). The success message names the Drafts folder, the APPEND UID, and the draft's own `Message-ID` (`draft id`) so a saved draft is never indistinguishable from a silent no-op and a later edit has a stable handle. Fails naming the mailbox when the server advertises no `\Drafts` special-use folder — no fallback folder is picked, unless a per-account `draftsFolder` override is configured (via `email-setup`), which is honoured before special-use discovery for hosts whose client-visible Drafts is a differently-named mailbox. Use when the operator wants to review and send by hand.
112
124
  - **Revise a draft:** `email-draft-edit` — replace a stored draft. The `targetUid` is a fast-path hint; the draft's own `Message-ID` (`draftMessageId`) is the stable identity, because Gmail renumbers a draft's UID when it is touched. The tool tries the UID first, then relocates by a `Message-ID` header search, then composes the replacement from the parameters (same shape as `email-draft`, plus `targetUid`, optional `draftMessageId`, and an optional `messageId` for a threaded revision), APPENDs it with `\Draft`, and deletes the resolved old copy — the replacement is always appended before the old copy is expunged, so a mid-cycle failure never leaves zero copies. When neither identity resolves a live draft, the prior draft is gone: the replacement is saved as a fresh draft (`created`) rather than failing. A single transient transport drop is retried on a fresh connection; auth and other permanent errors surface immediately. Nothing is sent. The confirmation names the folder, the new UID, and the new draft id.
125
+ - **Verify a draft:** `email-draft-list` — read the Drafts folder back and report what is in it. This is the only tool that can confirm a draft's presence: `email-status` reports connectivity and an inbox count, and `email-read`/`email-search` are inbox/sent only. Optional `uid` or `draftMessageId` narrows to one draft, but the plain listing is preferred, because neither handle is stable — Gmail renumbers a draft's Drafts UID when it is touched, and `email-draft-edit` mints a **new** draft `Message-ID` for every revision, so a handle from earlier in the conversation can miss a draft that is present. A narrowing miss therefore never returns bare: it names the miss, says why a stale handle misses, and lists the drafts that *are* in the folder, so "that handle is not here" can never be read as "no draft is here". Lists every message in the resolved folder, not only `\Draft`-flagged ones, because that is what the operator's own mail client shows and a `draftsFolder` override may point at a folder this plugin did not write. A draft id matching two rows is reported as two rows, not collapsed — the edit path leaves a second copy when an expunge is not confirmed. Nothing is written, sent or deleted.
126
+
127
+ **Confirm before asserting.** After `email-draft` or `email-draft-edit`, and whenever anyone reports a draft they cannot see, read the folder with `email-draft-list` before saying the draft is there. The save confirmation is evidence that the write succeeded, not evidence that the draft is in the folder now.
128
+
129
+ **Never state an unverified cause.** When the draft is confirmed present and the customer's client still does not show it, the honest report is exactly that: the draft is on the server, and the client-side reason is unknown. Do not offer a mail-app setting, a sync toggle or a provider option as the cause unless it has been verified. A successful IMAP write to and read of the Drafts folder proves the folder is exposed over IMAP, so provider "show in IMAP" settings are excluded by the evidence rather than being candidates for it.
130
+
113
131
  - **Send a draft:** `email-draft-send` — dispatch a stored draft addressed by its Drafts UID via SMTP, then relocate it into `\Sent`. Recipients come from the draft's own To/Cc/Bcc headers (the `\Drafts` copy keeps its `Bcc:`), deduped, sent from the agent alias with an explicit SMTP envelope. The draft is relocated only after SMTP accepts; an SMTP failure leaves it in place and surfaces verbatim. A draft with no recipients is refused before sending. Operator-approved sends only.
114
132
  - **The Sent copy is verified, not assumed.** All three send paths read `\Sent` back after SMTP acceptance and search it by Message-ID, then state what is actually there as a fact separate from the send. A folder and UID are named only when they were read back; "no copy was found", "this server advertises no `\Sent` folder", and "the Sent folder could not be checked" are reported as three distinct outcomes, never collapsed. A send whose copy cannot be located is still a successful send. Each send also appends its observed outcome to `config/email-sent-ledger.jsonl`, and the install heartbeat spawns `dist/scripts/sent-copy-sweep.js` hourly to ask `\Sent`, per mailbox, whether each Message-ID sent in the trailing 24 h is actually present (`[email-sent-sweep] op=reconcile …`). Presence is asked per Message-ID rather than by comparing folder totals, because `\Sent` also holds everything the owner sends from other clients. See [`references/email-reference.md`](references/email-reference.md).
115
133
  - **Ingestion — fetch:** `email-fetch` — list new IMAP messages since the stored high-water mark. Metadata + body preview only — including attachment names, mimetypes, and sizes so the operator sees what's attached before approving. No bytes downloaded, no graph writes. Stages the batch in-memory for `email-ingest`.
@@ -136,6 +154,7 @@ Email ingestion is **operator-initiated and human-in-the-loop**. There is no bac
136
154
 
137
155
  | Skill | Purpose |
138
156
  |-------|---------|
157
+ | [email-signature](skills/email-signature/SKILL.md) | Setting or changing the account's email signature. Carries the loader convention (three files at the account root, verbatim text/html, trimmed font), the rule that a signature is installed only via `email-signature-set`, and the rule that the agent never hand-builds one inside a draft body. |
139
158
  | [email-ingest](skills/email-ingest/SKILL.md) | Operator-initiated ingestion. Drives "check my emails" → list via `email-fetch` → summarise → ask the operator per message → write the approved subset via `email-ingest`. |
140
159
  | [email-composition](skills/email-composition/SKILL.md) | Conversational wrapper for drafting replies, new emails, and inbox triage. Orchestrates the sending `email-*` tools with human-in-the-loop approval on every send; draft intent ("leave it in drafts", "don't send yet") routes to `email-draft`, threaded drafts pass `messageId`, and a stored draft is revised via `email-draft-edit` or sent via `email-draft-send`. |
141
160
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=email-draft-list-tool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-draft-list-tool.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/email-draft-list-tool.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ // The tool's rendering is the unit under test, so the reader is stubbed at the
3
+ // module boundary. A wholesale vi.mock replaces the WHOLE module, so the factory
4
+ // must name every symbol email-draft-list.ts imports from it — MAX_LIMIT as well
5
+ // as listDrafts — or the import lands undefined.
6
+ const { listMock } = vi.hoisted(() => ({ listMock: vi.fn() }));
7
+ vi.mock("../lib/imap.js", () => ({
8
+ listDrafts: listMock,
9
+ MAX_LIMIT: 50,
10
+ }));
11
+ vi.mock("../lib/credentials.js", () => ({
12
+ loadCredentials: vi.fn(async () => ({
13
+ credentials: { config: { email: "m@x.com" }, password: "pw" },
14
+ })),
15
+ }));
16
+ const { emailDraftList } = await import("../tools/email-draft-list.js");
17
+ const run = (params = {}) => emailDraftList({ accountId: "acc-1", ...params });
18
+ const listing = (over) => ({
19
+ folder: "[Gmail]/Drafts",
20
+ mode: "census",
21
+ resolved: true,
22
+ rows: [],
23
+ total: 0,
24
+ ...over,
25
+ });
26
+ const quote = {
27
+ uid: 28316,
28
+ subject: "Quote",
29
+ to: ["jonathan@apexgrange.com"],
30
+ messageId: "<id-283@x>",
31
+ date: new Date("2026-07-28T09:00:00Z"),
32
+ };
33
+ describe("emailDraftList rendering", () => {
34
+ beforeEach(() => vi.clearAllMocks());
35
+ it("renders a census with the folder and every row", async () => {
36
+ listMock.mockResolvedValue(listing({
37
+ total: 2,
38
+ rows: [
39
+ quote,
40
+ { uid: 10, subject: "Older", to: ["a@x.com"], messageId: "<id-10@x>", date: null },
41
+ ],
42
+ }));
43
+ const text = await run();
44
+ expect(text).toContain("[Gmail]/Drafts");
45
+ expect(text).toContain("UID: 28316");
46
+ expect(text).toContain("jonathan@apexgrange.com");
47
+ expect(text).toContain("<id-283@x>");
48
+ expect(text).toContain("UID: 10");
49
+ });
50
+ it("an empty folder says so and is not an error", async () => {
51
+ listMock.mockResolvedValue(listing({ total: 0, rows: [] }));
52
+ const text = await run();
53
+ expect(text).toMatch(/No drafts in \[Gmail\]\/Drafts/);
54
+ });
55
+ it("a missed handle names the miss AND lists what is present", async () => {
56
+ listMock.mockResolvedValue(listing({ mode: "message-id", resolved: false, total: 1, rows: [quote] }));
57
+ const text = await run({ draftMessageId: "stale@x" });
58
+ expect(text).toContain("not found");
59
+ expect(text).toContain("UID: 28316");
60
+ expect(text).toMatch(/change when the draft is edited/);
61
+ });
62
+ it("a missed handle on a genuinely empty folder does not claim drafts exist", async () => {
63
+ listMock.mockResolvedValue(listing({ mode: "uid", resolved: false, total: 0, rows: [] }));
64
+ const text = await run({ uid: 999 });
65
+ expect(text).toContain("not found");
66
+ expect(text).toMatch(/no drafts at all/);
67
+ });
68
+ it("a confirmed hit reports the folder total alongside the draft", async () => {
69
+ listMock.mockResolvedValue(listing({ mode: "uid", resolved: true, total: 3, rows: [quote] }));
70
+ const text = await run({ uid: 28316 });
71
+ expect(text).toMatch(/Confirmed in \[Gmail\]\/Drafts/);
72
+ expect(text).toContain("3 drafts");
73
+ expect(text).toContain("UID: 28316");
74
+ });
75
+ it("names the duplicate-copy state when one draft id matches twice", async () => {
76
+ listMock.mockResolvedValue(listing({
77
+ mode: "message-id",
78
+ resolved: true,
79
+ total: 2,
80
+ rows: [quote, { ...quote, uid: 28317 }],
81
+ }));
82
+ const text = await run({ draftMessageId: "id-283@x" });
83
+ expect(text).toMatch(/2 copies match/);
84
+ expect(text).toMatch(/expunge is not confirmed/);
85
+ });
86
+ it("a miss over a truncated census states its window too", async () => {
87
+ listMock.mockResolvedValue(listing({
88
+ mode: "uid",
89
+ resolved: false,
90
+ total: 90,
91
+ rows: Array.from({ length: 20 }, (_, i) => ({
92
+ uid: i + 1,
93
+ subject: "s",
94
+ to: [],
95
+ messageId: null,
96
+ date: null,
97
+ })),
98
+ }));
99
+ const text = await run({ uid: 999 });
100
+ expect(text).toMatch(/holds 90 drafts; showing the newest 20/);
101
+ });
102
+ it("names both handles when both were tried", async () => {
103
+ listMock.mockResolvedValue(listing({ mode: "uid", resolved: false, total: 1, rows: [quote] }));
104
+ const text = await run({ uid: 999, draftMessageId: "gone@x" });
105
+ expect(text).toContain("UID 999");
106
+ expect(text).toContain("draft id gone@x");
107
+ });
108
+ it("truncation is stated and never mentions next_before_uid", async () => {
109
+ listMock.mockResolvedValue(listing({
110
+ folder: "Drafts",
111
+ total: 90,
112
+ rows: Array.from({ length: 50 }, (_, i) => ({
113
+ uid: i + 1,
114
+ subject: "s",
115
+ to: [],
116
+ messageId: null,
117
+ date: null,
118
+ })),
119
+ }));
120
+ const text = await run({ limit: 500 });
121
+ expect(text).toMatch(/newest 50 of 90/);
122
+ expect(text).not.toContain("next_before_uid");
123
+ });
124
+ it("clamps limit to MAX_LIMIT and floors it at 1", async () => {
125
+ listMock.mockResolvedValue(listing({}));
126
+ await run({ limit: 500 });
127
+ expect(listMock).toHaveBeenCalledWith(expect.anything(), "pw", expect.objectContaining({ limit: 50 }));
128
+ await run({ limit: 0 });
129
+ expect(listMock).toHaveBeenLastCalledWith(expect.anything(), "pw", expect.objectContaining({ limit: 1 }));
130
+ });
131
+ it("defaults the limit to 20 when none is given", async () => {
132
+ listMock.mockResolvedValue(listing({}));
133
+ await run();
134
+ expect(listMock).toHaveBeenCalledWith(expect.anything(), "pw", expect.objectContaining({ limit: 20 }));
135
+ });
136
+ it("says the listing is flag-agnostic so an operator can reconcile against their client", async () => {
137
+ listMock.mockResolvedValue(listing({ total: 1, rows: [quote] }));
138
+ const text = await run();
139
+ expect(text).toMatch(/not only \\Draft-flagged/);
140
+ });
141
+ });
142
+ //# sourceMappingURL=email-draft-list-tool.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-draft-list-tool.test.js","sourceRoot":"","sources":["../../src/__tests__/email-draft-list-tool.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=email-draft-list.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-draft-list.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/email-draft-list.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ const drafts = [
3
+ {
4
+ uid: 10,
5
+ envelope: {
6
+ subject: "Older",
7
+ to: [{ address: "a@x.com" }],
8
+ messageId: "<id-10@x>",
9
+ date: new Date("2026-07-01T00:00:00Z"),
10
+ },
11
+ },
12
+ {
13
+ uid: 28316,
14
+ envelope: {
15
+ subject: "Quote",
16
+ to: [{ address: "jonathan@apexgrange.com" }],
17
+ messageId: "<id-283@x>",
18
+ date: new Date("2026-07-28T00:00:00Z"),
19
+ },
20
+ },
21
+ ];
22
+ // The shared drafts mock in imap-drafts.test.ts has fetchOne but no fetch; a
23
+ // list reader iterates the async generator, so the fetch spy is composed in
24
+ // from source-fetch-efficiency.test.ts.
25
+ const mockClient = {
26
+ connect: vi.fn(async () => { }),
27
+ logout: vi.fn(async () => { }),
28
+ close: vi.fn(() => { }),
29
+ list: vi.fn(async () => [
30
+ { path: "INBOX" },
31
+ { path: "[Gmail]/Drafts", specialUse: "\\Drafts" },
32
+ ]),
33
+ getMailboxLock: vi.fn(async () => ({ release: vi.fn() })),
34
+ // Typed with the criteria parameter so per-test mockImplementation overrides
35
+ // that branch on it (header search vs {all:true}) remain assignable.
36
+ search: vi.fn(async (_criteria) => drafts.map((d) => d.uid)),
37
+ fetch: vi.fn(function (range) {
38
+ const uids = range.split(",").map((s) => Number(s.trim()));
39
+ async function* gen() {
40
+ for (const uid of uids) {
41
+ const row = drafts.find((d) => d.uid === uid);
42
+ if (row)
43
+ yield row;
44
+ }
45
+ }
46
+ return gen();
47
+ }),
48
+ };
49
+ vi.mock("imapflow", () => ({
50
+ ImapFlow: function ImapFlow() {
51
+ return mockClient;
52
+ },
53
+ }));
54
+ const { listDrafts, resetDraftsCensusForTest } = await import("../lib/imap.js");
55
+ const config = {
56
+ email: "m@x.com",
57
+ imapHost: "imap.x",
58
+ imapPort: 993,
59
+ imapSecurity: "tls",
60
+ };
61
+ const allUids = async (_criteria) => drafts.map((d) => d.uid);
62
+ describe("listDrafts", () => {
63
+ beforeEach(() => {
64
+ resetDraftsCensusForTest();
65
+ vi.clearAllMocks();
66
+ mockClient.search.mockImplementation(allUids);
67
+ });
68
+ it("census lists every draft in the resolved folder, newest first", async () => {
69
+ const r = await listDrafts(config, "pw", { limit: 20 });
70
+ expect(r.folder).toBe("[Gmail]/Drafts");
71
+ expect(r.mode).toBe("census");
72
+ expect(r.total).toBe(2);
73
+ expect(r.rows.map((x) => x.uid)).toEqual([28316, 10]);
74
+ expect(r.rows[0].to).toEqual(["jonathan@apexgrange.com"]);
75
+ expect(r.rows[0].messageId).toBe("<id-283@x>");
76
+ });
77
+ it("uid narrowing returns that draft and still reports the folder total", async () => {
78
+ const r = await listDrafts(config, "pw", { limit: 20, uid: 28316 });
79
+ expect(r.mode).toBe("uid");
80
+ expect(r.resolved).toBe(true);
81
+ expect(r.rows.map((x) => x.uid)).toEqual([28316]);
82
+ expect(r.total).toBe(2);
83
+ });
84
+ it("a uid that is absent returns resolved:false and the census rows", async () => {
85
+ const r = await listDrafts(config, "pw", { limit: 20, uid: 999 });
86
+ expect(r.resolved).toBe(false);
87
+ expect(r.total).toBe(2);
88
+ expect(r.rows.map((x) => x.uid)).toEqual([28316, 10]);
89
+ });
90
+ it("draftMessageId narrowing searches by header with angle brackets", async () => {
91
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [28316] : drafts.map((d) => d.uid));
92
+ const r = await listDrafts(config, "pw", { limit: 20, draftMessageId: "id-283@x" });
93
+ expect(r.mode).toBe("message-id");
94
+ expect(r.resolved).toBe(true);
95
+ expect(r.rows.map((x) => x.uid)).toEqual([28316]);
96
+ expect(mockClient.search).toHaveBeenCalledWith({ header: { "message-id": "<id-283@x>" } }, { uid: true });
97
+ });
98
+ it("a stale draft id misses but the response still carries the drafts that are present", async () => {
99
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [] : drafts.map((d) => d.uid));
100
+ const r = await listDrafts(config, "pw", { limit: 20, draftMessageId: "pre-edit@x" });
101
+ expect(r.resolved).toBe(false);
102
+ expect(r.total).toBe(2);
103
+ expect(r.rows.map((x) => x.uid)).toEqual([28316, 10]);
104
+ });
105
+ it("reports every draft matching one draft id, not just the first", async () => {
106
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [10, 28316] : drafts.map((d) => d.uid));
107
+ const r = await listDrafts(config, "pw", { limit: 20, draftMessageId: "dupe@x" });
108
+ expect(r.rows).toHaveLength(2);
109
+ });
110
+ it("relocates by draftMessageId when the uid is stale, like replaceDraft", async () => {
111
+ // The incident shape: the caller holds a UID from before an edit plus the
112
+ // draft id, and Gmail has renumbered the draft.
113
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [28316] : drafts.map((d) => d.uid));
114
+ const r = await listDrafts(config, "pw", {
115
+ limit: 20,
116
+ uid: 999,
117
+ draftMessageId: "id-283@x",
118
+ });
119
+ expect(r.resolved).toBe(true);
120
+ expect(r.rows.map((x) => x.uid)).toEqual([28316]);
121
+ });
122
+ it("only falls through to the relocate when the uid actually misses", async () => {
123
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [10] : drafts.map((d) => d.uid));
124
+ const r = await listDrafts(config, "pw", {
125
+ limit: 20,
126
+ uid: 28316,
127
+ draftMessageId: "id-10@x",
128
+ });
129
+ expect(r.rows.map((x) => x.uid)).toEqual([28316]);
130
+ // The UID hit, so no header search was issued.
131
+ expect(mockClient.search).toHaveBeenCalledTimes(1);
132
+ });
133
+ it("reports unresolved when both handles miss", async () => {
134
+ mockClient.search.mockImplementation(async (criteria) => "header" in criteria ? [] : drafts.map((d) => d.uid));
135
+ const r = await listDrafts(config, "pw", { limit: 20, uid: 999, draftMessageId: "gone@x" });
136
+ expect(r.resolved).toBe(false);
137
+ expect(r.total).toBe(2);
138
+ });
139
+ it("an empty folder returns total 0 with no rows and does not throw", async () => {
140
+ mockClient.search.mockImplementation(async () => []);
141
+ const r = await listDrafts(config, "pw", { limit: 20 });
142
+ expect(r.total).toBe(0);
143
+ expect(r.rows).toEqual([]);
144
+ expect(mockClient.fetch).not.toHaveBeenCalled();
145
+ });
146
+ it("clamps the census window to the limit while reporting the true total", async () => {
147
+ const r = await listDrafts(config, "pw", { limit: 1 });
148
+ expect(r.rows).toHaveLength(1);
149
+ expect(r.rows[0].uid).toBe(28316);
150
+ expect(r.total).toBe(2);
151
+ });
152
+ });
153
+ //# sourceMappingURL=email-draft-list.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-draft-list.test.js","sourceRoot":"","sources":["../../src/__tests__/email-draft-list.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=email-signature-set.test.d.ts.map