kennel 0.2.3 → 0.2.5

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 (237) hide show
  1. package/README.md +23 -13
  2. package/dist/cli/dispatch.js +16 -3
  3. package/dist/cli/src/cli/dispatch.js +16 -3
  4. package/dist/cli/src/cli/usage.js +1 -1
  5. package/dist/cli/src/core/command.js +1 -0
  6. package/dist/cli/src/core/init.js +5 -5
  7. package/dist/cli/src/plugins/external.d.ts +28 -2
  8. package/dist/cli/src/plugins/external.js +75 -21
  9. package/dist/cli/src/site/server-plugins.d.ts +2 -7
  10. package/dist/cli/src/site/viewer.js +1 -0
  11. package/dist/cli/usage.js +1 -1
  12. package/dist/core/command.js +1 -0
  13. package/dist/core/init.js +5 -5
  14. package/dist/core/runtime.js +357 -3207
  15. package/dist/package/preview.js +1232 -5113
  16. package/dist/plugins/external.d.ts +28 -2
  17. package/dist/plugins/external.js +75 -21
  18. package/dist/site/server-plugins.d.ts +2 -7
  19. package/dist/site/server-plugins.js +552 -10447
  20. package/dist/site/viewer.js +1 -0
  21. package/kernel/apps/web/package.json +1 -5
  22. package/kernel/apps/web/scripts/dev.mjs +53 -0
  23. package/kernel/apps/web/src/app.css +0 -3
  24. package/kernel/apps/web/src/main.tsx +0 -1
  25. package/kernel/apps/web/src/plugins.ts +0 -30
  26. package/kernel/apps/web/tsconfig.json +1 -2
  27. package/kernel/apps/web/vite.config.ts +31 -13
  28. package/kernel/packages/api/package.json +0 -4
  29. package/kernel/packages/api/src/server/folder-metadata-plugin.ts +13 -38
  30. package/kernel/packages/api/src/server/index.ts +0 -1
  31. package/kernel/packages/api/src/server/plugins.ts +49 -16
  32. package/kernel/packages/api/test/core.test.ts +13 -25
  33. package/kernel/packages/markdown/README.md +1 -1
  34. package/package.json +1 -1
  35. package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
  36. package/web/assets/app-CX20nNIa.js +2 -0
  37. package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
  38. package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
  39. package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
  40. package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
  41. package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
  42. package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
  43. package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
  44. package/web/assets/channel-X7WHyxxT.js +1 -0
  45. package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
  46. package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
  47. package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
  48. package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
  49. package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
  50. package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
  51. package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
  52. package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
  53. package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
  54. package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
  55. package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
  56. package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
  57. package/web/assets/client-7OWw5kW2.js +6 -0
  58. package/web/assets/client-DYj_td4Q.js +17 -0
  59. package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
  60. package/web/assets/command-zkzH4hVz.js +38 -0
  61. package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
  62. package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
  63. package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
  64. package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
  65. package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
  66. package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
  67. package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
  68. package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
  69. package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
  70. package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
  71. package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
  72. package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
  73. package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
  74. package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
  75. package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
  76. package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
  77. package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
  78. package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
  79. package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
  80. package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
  81. package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
  82. package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
  83. package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
  84. package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
  85. package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
  86. package/web/assets/index-CegvVQ1C.js +1 -0
  87. package/web/assets/index-Dwhf8F_U.css +1 -0
  88. package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
  89. package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
  90. package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
  91. package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
  92. package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
  93. package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
  94. package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
  95. package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
  96. package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
  97. package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
  98. package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
  99. package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
  100. package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
  101. package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
  102. package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
  103. package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
  104. package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
  105. package/web/assets/select-CtXC_PLC.js +30 -0
  106. package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
  107. package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
  108. package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
  109. package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
  110. package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
  111. package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
  112. package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
  113. package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
  114. package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
  115. package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
  116. package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
  117. package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
  118. package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
  119. package/web/index.html +2 -2
  120. package/kernel/packages/api/test/partial-loading.test.ts +0 -43
  121. package/kernel/plugins/calendar/SKILL.md +0 -173
  122. package/kernel/plugins/calendar/eslint.config.js +0 -10
  123. package/kernel/plugins/calendar/package.json +0 -73
  124. package/kernel/plugins/calendar/schema.json +0 -137
  125. package/kernel/plugins/calendar/src/calendar.tsx +0 -328
  126. package/kernel/plugins/calendar/src/client.tsx +0 -139
  127. package/kernel/plugins/calendar/src/context.ts +0 -48
  128. package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
  129. package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
  130. package/kernel/plugins/calendar/src/index.ts +0 -4
  131. package/kernel/plugins/calendar/src/manifest.ts +0 -7
  132. package/kernel/plugins/calendar/src/parse.ts +0 -610
  133. package/kernel/plugins/calendar/src/server.ts +0 -160
  134. package/kernel/plugins/calendar/src/utils.ts +0 -80
  135. package/kernel/plugins/calendar/src/views.tsx +0 -418
  136. package/kernel/plugins/calendar/test/context.test.mjs +0 -60
  137. package/kernel/plugins/calendar/test/package.test.mjs +0 -43
  138. package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
  139. package/kernel/plugins/calendar/test/server.test.mjs +0 -126
  140. package/kernel/plugins/calendar/tsconfig.json +0 -17
  141. package/kernel/plugins/mail/SKILL.md +0 -97
  142. package/kernel/plugins/mail/eslint.config.js +0 -10
  143. package/kernel/plugins/mail/package.json +0 -79
  144. package/kernel/plugins/mail/src/api.ts +0 -359
  145. package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
  146. package/kernel/plugins/mail/src/client.tsx +0 -14
  147. package/kernel/plugins/mail/src/flock.ts +0 -137
  148. package/kernel/plugins/mail/src/folders.ts +0 -37
  149. package/kernel/plugins/mail/src/history.ts +0 -485
  150. package/kernel/plugins/mail/src/index.ts +0 -3
  151. package/kernel/plugins/mail/src/mail-card.tsx +0 -123
  152. package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
  153. package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
  154. package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
  155. package/kernel/plugins/mail/src/mail-page.tsx +0 -109
  156. package/kernel/plugins/mail/src/mail-request.ts +0 -78
  157. package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
  158. package/kernel/plugins/mail/src/mailos.ts +0 -723
  159. package/kernel/plugins/mail/src/manifest.ts +0 -7
  160. package/kernel/plugins/mail/src/node.ts +0 -171
  161. package/kernel/plugins/mail/src/parse.ts +0 -139
  162. package/kernel/plugins/mail/src/server.ts +0 -125
  163. package/kernel/plugins/mail/src/types.ts +0 -10
  164. package/kernel/plugins/mail/src/viewer.tsx +0 -147
  165. package/kernel/plugins/mail/test/flock.test.mjs +0 -156
  166. package/kernel/plugins/mail/test/folders.test.mjs +0 -138
  167. package/kernel/plugins/mail/test/history.test.mjs +0 -460
  168. package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
  169. package/kernel/plugins/mail/test/node.test.mjs +0 -207
  170. package/kernel/plugins/mail/test/package.test.mjs +0 -54
  171. package/kernel/plugins/mail/test/parse.test.mjs +0 -32
  172. package/kernel/plugins/mail/test/server.test.mjs +0 -116
  173. package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
  174. package/kernel/plugins/mail/tsconfig.json +0 -17
  175. package/kernel/plugins/projects/SKILL.md +0 -73
  176. package/kernel/plugins/projects/eslint.config.js +0 -10
  177. package/kernel/plugins/projects/package.json +0 -70
  178. package/kernel/plugins/projects/src/client.tsx +0 -17
  179. package/kernel/plugins/projects/src/data/model.ts +0 -129
  180. package/kernel/plugins/projects/src/index.ts +0 -17
  181. package/kernel/plugins/projects/src/manifest.ts +0 -7
  182. package/kernel/plugins/projects/src/project-context.ts +0 -41
  183. package/kernel/plugins/projects/src/server.ts +0 -276
  184. package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
  185. package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
  186. package/kernel/plugins/projects/test/package.test.mjs +0 -51
  187. package/kernel/plugins/projects/tsconfig.json +0 -17
  188. package/kernel/plugins/tasks/SKILL.md +0 -341
  189. package/kernel/plugins/tasks/eslint.config.js +0 -10
  190. package/kernel/plugins/tasks/package.json +0 -74
  191. package/kernel/plugins/tasks/schema.json +0 -939
  192. package/kernel/plugins/tasks/src/api.ts +0 -212
  193. package/kernel/plugins/tasks/src/client.tsx +0 -23
  194. package/kernel/plugins/tasks/src/data/parse.ts +0 -544
  195. package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
  196. package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
  197. package/kernel/plugins/tasks/src/index.ts +0 -34
  198. package/kernel/plugins/tasks/src/manifest.ts +0 -7
  199. package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
  200. package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
  201. package/kernel/plugins/tasks/src/server.ts +0 -447
  202. package/kernel/plugins/tasks/src/styles.css +0 -9
  203. package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
  204. package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
  205. package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
  206. package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
  207. package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
  208. package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
  209. package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
  210. package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
  211. package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
  212. package/kernel/plugins/tasks/test/api.test.mjs +0 -99
  213. package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
  214. package/kernel/plugins/tasks/test/package.test.mjs +0 -54
  215. package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
  216. package/kernel/plugins/tasks/test/server.test.mjs +0 -386
  217. package/kernel/plugins/tasks/tsconfig.json +0 -17
  218. package/web/assets/app-DoGr2Zi7.js +0 -2
  219. package/web/assets/channel-uMEjV8qc.js +0 -1
  220. package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
  221. package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
  222. package/web/assets/client-C6Nxdfif.js +0 -2
  223. package/web/assets/client-CXYfBko5.js +0 -17
  224. package/web/assets/client-CoKtOq-X.js +0 -7
  225. package/web/assets/client-DyRQz5RU.js +0 -53
  226. package/web/assets/client-OY6HUzuD.js +0 -98
  227. package/web/assets/client-YOYWZIFi.js +0 -34
  228. package/web/assets/command-BugZBGXE.js +0 -38
  229. package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
  230. package/web/assets/index-BaRnbYRw.js +0 -1
  231. package/web/assets/index-BexmLxIP.css +0 -1
  232. package/web/assets/index-DDirZMgx.js +0 -1
  233. package/web/assets/note-card-DBfX6gW1.js +0 -6
  234. package/web/assets/select-D8cqNgcp.js +0 -30
  235. package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
  236. package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
  237. package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
@@ -1,7 +0,0 @@
1
- import type { FolderPluginManifest } from "@raggle-ai/folder-plugin"
2
-
3
- /** Stable Mail plugin identity and navigation label. No side effects. */
4
- export const mailManifest: FolderPluginManifest = {
5
- id: "mail",
6
- label: "Mail",
7
- }
@@ -1,171 +0,0 @@
1
- import { createHash } from "node:crypto"
2
- import { realpathSync } from "node:fs"
3
- import { readdir, readFile } from "node:fs/promises"
4
- import { homedir } from "node:os"
5
- import { isAbsolute, relative, resolve, sep } from "node:path"
6
-
7
- import type { Mail, MailListItem } from "./parse.ts"
8
- import { mailExcerpt, parseMailMarkdown } from "./parse.ts"
9
-
10
- export type ListMailOptions = {
11
- mailDir?: string
12
- folder: string
13
- }
14
-
15
- export function defaultMailDir() {
16
- return resolve(homedir(), ".email/drafts")
17
- }
18
-
19
- function draftFilePath(mailDir: string, id: string) {
20
- const trimmed = id.trim()
21
- if (
22
- !trimmed ||
23
- trimmed === "." ||
24
- trimmed === ".." ||
25
- /[\\/]/.test(trimmed)
26
- ) {
27
- throw new Error("Invalid mail id")
28
- }
29
- return resolve(mailDir, `${trimmed}.md`)
30
- }
31
-
32
- /**
33
- * Drafts currently being worked on. MailOS keeps sent and archived drafts in
34
- * the same folder, so the reader must exclude them here to show the same
35
- * current draft set without invoking the MailOS binary.
36
- */
37
- const activeStatuses = new Set(["working", "ready"])
38
-
39
- function isActiveStatus(status: string | null) {
40
- return status !== null && activeStatuses.has(status)
41
- }
42
-
43
- function canonicalDraftPath(path: string): string | null {
44
- try {
45
- return realpathSync(resolve(path))
46
- } catch {
47
- return null
48
- }
49
- }
50
-
51
- /**
52
- * True when `candidate` is `root` itself or a real descendant directory of
53
- * `root`. Both paths must exist on disk and are resolved through realpath, so
54
- * sibling-prefix paths, outside ancestors, traversal escapes, and symlink
55
- * escapes are rejected; an unresolved (missing) folder is denied rather than
56
- * guessed. Shared by list and detail authorization so draft membership is
57
- * consistent everywhere.
58
- */
59
- export function isFolderWithin(
60
- root: string,
61
- candidate: string | null | undefined,
62
- ): boolean {
63
- if (!candidate) return false
64
- const anchor = canonicalDraftPath(root)
65
- if (anchor === null) return false
66
- const target = canonicalDraftPath(candidate)
67
- if (target === null) return false
68
- if (anchor === target) return true
69
- const rel = relative(anchor, target)
70
- if (!rel) return true
71
- if (rel === ".." || rel.startsWith(`..${sep}`)) return false
72
- return !isAbsolute(rel)
73
- }
74
-
75
- function sortDateKey(value: string | null) {
76
- const parsed = value ? Date.parse(value) : Number.NaN
77
- if (Number.isNaN(parsed)) return 0
78
- return parsed
79
- }
80
-
81
- function sortMail(items: MailListItem[]) {
82
- items.sort((a, b) => {
83
- const aKey = sortDateKey(a.updatedAt ?? a.createdAt)
84
- const bKey = sortDateKey(b.updatedAt ?? b.createdAt)
85
- return bKey - aKey
86
- })
87
- return items
88
- }
89
-
90
- function draftRevision(content: string) {
91
- return createHash("sha256").update(content).digest("hex")
92
- }
93
-
94
- async function mailFromFile(
95
- filePath: string,
96
- ): Promise<(Mail & { filePath: string; revision: string }) | null> {
97
- const content = await readFile(filePath, "utf8").catch(() => null)
98
- if (!content) return null
99
- const parsed = parseMailMarkdown(content)
100
- if (!parsed.id) return null
101
- if (!isActiveStatus(parsed.status)) return null
102
-
103
- return {
104
- id: parsed.id,
105
- status: parsed.status,
106
- tags: parsed.tags,
107
- folder: parsed.folder,
108
- attachments: parsed.attachments,
109
- from: parsed.from,
110
- inReplyTo: parsed.inReplyTo,
111
- references: parsed.references,
112
- to: parsed.to,
113
- cc: parsed.cc,
114
- bcc: parsed.bcc,
115
- subject: parsed.subject,
116
- createdAt: parsed.createdAt,
117
- updatedAt: parsed.updatedAt,
118
- noSignature: parsed.noSignature,
119
- body: parsed.body,
120
- filePath,
121
- revision: draftRevision(content),
122
- }
123
- }
124
-
125
- export async function listMailForFolder({
126
- mailDir = defaultMailDir(),
127
- folder,
128
- }: ListMailOptions): Promise<MailListItem[]> {
129
- const entries = await readdir(mailDir, { withFileTypes: true })
130
- const items: MailListItem[] = []
131
-
132
- for (const entry of entries) {
133
- if (!entry.isFile()) continue
134
- if (!entry.name.endsWith(".md")) continue
135
-
136
- const filePath = resolve(mailDir, entry.name)
137
- const mail = await mailFromFile(filePath)
138
- if (!mail) continue
139
- if (!isFolderWithin(folder, mail.folder)) continue
140
-
141
- items.push({
142
- id: mail.id,
143
- status: mail.status,
144
- tags: mail.tags,
145
- folder: mail.folder,
146
- attachments: mail.attachments,
147
- from: mail.from,
148
- inReplyTo: mail.inReplyTo,
149
- references: mail.references,
150
- to: mail.to,
151
- cc: mail.cc,
152
- bcc: mail.bcc,
153
- subject: mail.subject,
154
- createdAt: mail.createdAt,
155
- updatedAt: mail.updatedAt,
156
- noSignature: mail.noSignature,
157
- excerpt: mailExcerpt(mail.body),
158
- filePath,
159
- revision: mail.revision,
160
- })
161
- }
162
-
163
- return sortMail(items)
164
- }
165
-
166
- export async function readMailById(
167
- id: string,
168
- mailDir = defaultMailDir(),
169
- ): Promise<(Mail & { filePath: string; revision: string }) | null> {
170
- return mailFromFile(draftFilePath(mailDir, id))
171
- }
@@ -1,139 +0,0 @@
1
- import { parse as parseYaml } from "yaml"
2
-
3
- export type MailStatus = "working" | "ready" | "sent" | "canceled" | string
4
-
5
- export type Mail = {
6
- id: string
7
- status: MailStatus | null
8
- tags: string[]
9
- folder: string | null
10
- attachments: string[]
11
- from: string | null
12
- inReplyTo: string | null
13
- references: string[]
14
- to: string[]
15
- cc: string[]
16
- bcc: string[]
17
- subject: string | null
18
- createdAt: string | null
19
- updatedAt: string | null
20
- /** Draft frontmatter `no_signature: true`: suppresses any signature on send. */
21
- noSignature: boolean
22
- body: string
23
- }
24
-
25
- export type MailListItem = Omit<Mail, "body"> & {
26
- excerpt: string
27
- filePath: string
28
- /**
29
- * sha256 of the exact draft file bytes. MailOS treats this hash as the
30
- * draft revision, so guarded destructive actions can prove the file has not
31
- * changed since the client last loaded it.
32
- */
33
- revision: string
34
- }
35
-
36
- function stringOrNull(value: unknown) {
37
- return typeof value === "string" && value.trim() ? value : null
38
- }
39
-
40
- function boolValue(value: unknown) {
41
- return value === true || value === "true"
42
- }
43
-
44
- function stringList(value: unknown): string[] {
45
- if (!value) return []
46
- if (Array.isArray(value))
47
- return value
48
- .filter((item): item is string => typeof item === "string")
49
- .map((item) => item.trim())
50
- .filter(Boolean)
51
- if (typeof value === "string") return value.trim() ? [value.trim()] : []
52
- return []
53
- }
54
-
55
- export function parseMailMarkdown(content: string): Omit<Mail, "id"> & {
56
- id: string | null
57
- } {
58
- if (!content.startsWith("---\n")) {
59
- return {
60
- id: null,
61
- status: null,
62
- tags: [],
63
- folder: null,
64
- attachments: [],
65
- from: null,
66
- inReplyTo: null,
67
- references: [],
68
- to: [],
69
- cc: [],
70
- bcc: [],
71
- subject: null,
72
- createdAt: null,
73
- updatedAt: null,
74
- noSignature: false,
75
- body: content,
76
- }
77
- }
78
-
79
- const end = content.indexOf("\n---\n")
80
- if (end < 0) {
81
- return {
82
- id: null,
83
- status: null,
84
- tags: [],
85
- folder: null,
86
- attachments: [],
87
- from: null,
88
- inReplyTo: null,
89
- references: [],
90
- to: [],
91
- cc: [],
92
- bcc: [],
93
- subject: null,
94
- createdAt: null,
95
- updatedAt: null,
96
- noSignature: false,
97
- body: content,
98
- }
99
- }
100
-
101
- const frontmatterRaw = content.slice(4, end)
102
- const body = content.slice(end + "\n---\n".length).replace(/^\n+/, "")
103
-
104
- const frontmatterValue = parseYaml(frontmatterRaw) as unknown
105
- const frontmatter =
106
- frontmatterValue &&
107
- typeof frontmatterValue === "object" &&
108
- !Array.isArray(frontmatterValue)
109
- ? (frontmatterValue as Record<string, unknown>)
110
- : {}
111
-
112
- return {
113
- id: stringOrNull(frontmatter.id),
114
- status: stringOrNull(frontmatter.status),
115
- tags: stringList(frontmatter.tags),
116
- folder: stringOrNull(frontmatter.folder),
117
- attachments: stringList(frontmatter.file),
118
- from: stringOrNull(frontmatter.from),
119
- inReplyTo: stringOrNull(frontmatter.in_reply_to),
120
- references: stringList(frontmatter.references),
121
- to: stringList(frontmatter.to),
122
- cc: stringList(frontmatter.cc),
123
- bcc: stringList(frontmatter.bcc),
124
- subject: stringOrNull(frontmatter.subject),
125
- createdAt: stringOrNull(frontmatter.created_at),
126
- updatedAt: stringOrNull(frontmatter.updated_at),
127
- noSignature: boolValue(frontmatter.no_signature),
128
- body,
129
- }
130
- }
131
-
132
- export function mailExcerpt(body: string, maxChars = 280) {
133
- const normalized = body
134
- .trim()
135
- .replace(/\r\n/g, "\n")
136
- .replace(/\n{3,}/g, "\n\n")
137
- if (normalized.length <= maxChars) return normalized
138
- return `${normalized.slice(0, maxChars).trimEnd()}…`
139
- }
@@ -1,125 +0,0 @@
1
- import type {
2
- FolderPluginContext,
3
- FolderPluginData,
4
- FolderPluginDetection,
5
- } from "@raggle-ai/folder-plugin/server"
6
- import { FolderPluginFailure } from "@raggle-ai/folder-plugin/server"
7
-
8
- import type { Mail, MailListItem } from "./parse.ts"
9
- import type { MailPreview } from "./types.ts"
10
- import { createMailosService } from "./mailos.ts"
11
- import { mailManifest } from "./manifest.ts"
12
- import {
13
- defaultMailDir,
14
- isFolderWithin,
15
- listMailForFolder,
16
- readMailById,
17
- } from "./node.ts"
18
-
19
- export type MailCreatePluginOptions = {
20
- /**
21
- * Override MailOS mail directory. Intended for tests.
22
- * Defaults to ~/.email/drafts.
23
- */
24
- mailDir?: string
25
- /** Preview execution hook. Fallback reads the draft body without a composer. */
26
- previewer?: (options: {
27
- folder: string
28
- id: string
29
- mail: NonNullable<Awaited<ReturnType<typeof readMailById>>>
30
- }) => Promise<MailPreview>
31
- }
32
-
33
- type DetailMail = Mail & { filePath: string; revision: string }
34
-
35
- function detailData(mail: DetailMail, preview: MailPreview): FolderPluginData {
36
- return {
37
- kind: "available",
38
- data: {
39
- mail: {
40
- ...mail,
41
- bcc: mail.bcc,
42
- body: mail.body,
43
- cc: mail.cc,
44
- createdAt: mail.createdAt,
45
- filePath: mail.filePath,
46
- folder: mail.folder,
47
- id: mail.id,
48
- status: mail.status,
49
- subject: mail.subject,
50
- tags: mail.tags,
51
- to: mail.to,
52
- updatedAt: mail.updatedAt,
53
- noSignature: mail.noSignature,
54
- },
55
- preview,
56
- },
57
- }
58
- }
59
-
60
- export function createMailPlugin({
61
- mailDir = defaultMailDir(),
62
- previewer,
63
- }: MailCreatePluginOptions = {}) {
64
- const mailos = createMailosService()
65
- const compose =
66
- previewer ??
67
- (async ({ mail }) => ({
68
- body: mail.body,
69
- // A no_signature draft sends without any signature; do not show one.
70
- signature: mail.noSignature
71
- ? null
72
- : mail.folder
73
- ? await mailos.signature(mail.folder)
74
- : null,
75
- }))
76
-
77
- return {
78
- ...mailManifest,
79
- async detect(context: FolderPluginContext): Promise<FolderPluginDetection> {
80
- const items = await listMailForFolder({ folder: context.folder, mailDir })
81
- if (!items.length) return { status: "unavailable" }
82
- return { count: items.length, status: "available" }
83
- },
84
- async handle(
85
- context: FolderPluginContext,
86
- subpath: string,
87
- ): Promise<FolderPluginData> {
88
- const folder = context.folder
89
-
90
- if (subpath === "") {
91
- const items = await listMailForFolder({ folder, mailDir })
92
- if (!items.length) {
93
- return {
94
- kind: "available",
95
- data: { folder, items: [], status: "unavailable" },
96
- }
97
- }
98
- return {
99
- kind: "available",
100
- data: { folder, items, status: "available" },
101
- }
102
- }
103
-
104
- const id = subpath.trim()
105
- const mail = await readMailById(id, mailDir)
106
- if (!mail) throw new Error("Mail not found")
107
- if (!mail.folder) throw new Error("Mail has no folder")
108
- if (!isFolderWithin(folder, mail.folder))
109
- throw new Error("Mail not found for this folder")
110
-
111
- let preview: MailPreview
112
- try {
113
- preview = await compose({ folder, id: mail.id, mail })
114
- } catch (error) {
115
- const message =
116
- error instanceof Error ? error.message : "Unknown preview error"
117
- throw new FolderPluginFailure(`Mail preview failed: ${message}`)
118
- }
119
-
120
- return detailData(mail, preview)
121
- },
122
- }
123
- }
124
-
125
- export type { MailListItem }
@@ -1,10 +0,0 @@
1
- export type MailPreview = {
2
- /**
3
- * Plain body of the draft read directly from its Markdown file.
4
- */
5
- body: string
6
- /**
7
- * Appended signature text. Null when no signature is present.
8
- */
9
- signature: string | null
10
- }
@@ -1,147 +0,0 @@
1
- import type { ReactNode } from "react"
2
-
3
- import { FolderFilterBar } from "@raggle-ai/ui/components/folder-filter-bar"
4
-
5
- import type { MailFolderChoice } from "./folders.ts"
6
- import type { MailPreview } from "./types.ts"
7
- import { MailCard } from "./mail-card.tsx"
8
-
9
- export type MailListItem = {
10
- attachments: string[]
11
- bcc: string[]
12
- cc: string[]
13
- createdAt: string | null
14
- excerpt: string
15
- filePath: string
16
- folder: string | null
17
- from: string | null
18
- id: string
19
- inReplyTo: string | null
20
- references: string[]
21
- revision: string
22
- status: string | null
23
- subject: string | null
24
- tags: string[]
25
- to: string[]
26
- updatedAt: string | null
27
- noSignature: boolean
28
- }
29
-
30
- export type MailDetail = Omit<MailListItem, "excerpt"> & { body: string }
31
-
32
- export type { MailPreview } from "./types.ts"
33
-
34
- export type MailListAction = "archive" | "delete"
35
-
36
- export type MailContextInput = {
37
- attachments?: ReadonlyArray<string>
38
- bcc?: ReadonlyArray<string>
39
- cc?: ReadonlyArray<string>
40
- excerpt?: string
41
- filePath: string
42
- folder?: string | null
43
- from?: string | null
44
- status?: string | null
45
- subject?: string | null
46
- tags?: ReadonlyArray<string>
47
- to?: ReadonlyArray<string>
48
- }
49
-
50
- /**
51
- * Agent-friendly text context for one mail item, in the same shape the tasks
52
- * plugin copies (`todoItemContext`). Callers pass the item they have — a list
53
- * item (excerpt only) or a detail (full body via the second argument).
54
- */
55
- export const mailItemContext = (item: MailContextInput, body?: string) =>
56
- [
57
- "You are helping with an email draft. Use the context below to understand the message and what it needs next.",
58
- "",
59
- "Instructions for the LLM:",
60
- "- Treat the subject as the draft's objective.",
61
- "- Use recipients, tags and body (or the owning draft file) as the authoritative message context.",
62
- "- If information is missing, state the exact missing input or recipient needed.",
63
- "- Return: 1) current status, 2) what the message needs next, 3) suggested edits or reply if asked.",
64
- "",
65
- "Mail context:",
66
- `- Subject: ${item.subject ?? "(no subject)"}`,
67
- item.from ? `- From: ${item.from}` : null,
68
- item.to?.length ? `- To: ${item.to.join(", ")}` : null,
69
- item.cc?.length ? `- Cc: ${item.cc.join(", ")}` : null,
70
- item.bcc?.length ? `- Bcc: ${item.bcc.join(", ")}` : null,
71
- item.attachments?.length
72
- ? `- Attachments: ${item.attachments.join(", ")}`
73
- : null,
74
- `- Status: ${item.status ?? "unknown"}`,
75
- item.folder ? `- Folder: ${item.folder}` : null,
76
- item.tags?.length
77
- ? `- Tags: ${item.tags.map((tag) => `#${tag}`).join(" ")}`
78
- : null,
79
- `- Draft file: ${item.filePath}`,
80
- body ? "" : null,
81
- body ? "Draft body:" : null,
82
- body ?? null,
83
- !body && item.excerpt ? "" : null,
84
- !body && item.excerpt ? "Draft excerpt:" : null,
85
- !body && item.excerpt ? item.excerpt : null,
86
- ]
87
- .filter((line): line is string => line != null)
88
- .join("\n")
89
-
90
- export type MailDetailResponse = {
91
- mail: MailDetail
92
- preview?: MailPreview
93
- }
94
-
95
- export function MailFolderFilter({
96
- choices,
97
- onSelect,
98
- selected,
99
- total,
100
- }: {
101
- choices: MailFolderChoice[]
102
- onSelect: (folders: readonly string[]) => void
103
- selected: readonly string[]
104
- total: number
105
- }) {
106
- return (
107
- <FolderFilterBar
108
- allLabel="All folders"
109
- ariaLabel="Filter drafts by folder"
110
- choices={choices}
111
- onSelect={onSelect}
112
- selected={selected}
113
- total={total}
114
- />
115
- )
116
- }
117
-
118
- export function MailList({
119
- actions,
120
- folderLabel,
121
- mailHref,
122
- items,
123
- }: {
124
- /**
125
- * Optional per-draft action menu rendered in the card corner. The mail
126
- * package stays UI-framework agnostic: the web layer passes its own menu
127
- * (e.g. a shadcn dropdown) through this render slot.
128
- */
129
- actions?: (item: MailListItem) => ReactNode
130
- folderLabel?: (folder: string | null) => string | null
131
- mailHref: (id: string) => string
132
- items: MailListItem[]
133
- }) {
134
- return (
135
- <ol className="m-0 grid list-none grid-cols-1 gap-2 p-0">
136
- {items.map((item) => (
137
- <MailCard
138
- actions={actions}
139
- folderLabel={folderLabel?.(item.folder)}
140
- href={mailHref(item.id)}
141
- item={item}
142
- key={item.id}
143
- />
144
- ))}
145
- </ol>
146
- )
147
- }