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,97 +0,0 @@
1
- ---
2
- name: kennel-mail
3
- description: Open and read local MailOS email drafts for a Kennel folder — Markdown draft files in `~/.email/drafts` with YAML frontmatter, filtered to drafts whose `folder` is the viewed folder or one of its subfolders. Use when the user asks to view drafts for a folder in the Kennel viewer, read mail drafts by subject, status, tags, or recipient, list which drafts belong to a workspace, open a specific draft, or see a draft's excerpt and revision.
4
- ---
5
-
6
- # Kennel Mail Plugin
7
-
8
- The Mail plugin (`@raggle-ai/mail`) shows local MailOS email drafts —
9
- Markdown files with YAML frontmatter in `~/.email/drafts/*.md` — scoped to the
10
- folder the user is viewing. It offers a folder selector, a draft list, and a
11
- per-draft detail view, without invoking the MailOS binary.
12
-
13
- This skill covers the agent-facing parts of that plugin: where drafts live,
14
- which drafts are visible for a folder, and how to open and verify the view.
15
- For the plugin package's implementation contract, see `plugins/README.md`
16
- in the Kennel repo.
17
-
18
- ## When to use
19
-
20
- - The user asks to open, list, or read email drafts for a folder in Kennel.
21
- - The user wants to know which drafts belong to a workspace, or see a draft's
22
- subject, status, tags, recipients, excerpt, or revision.
23
- - Preview or verification work needs the Mail plugin tab to be available and
24
- populated for a target folder.
25
-
26
- ## Workflow
27
-
28
- 1. **Identify the folder** whose drafts the user wants. The mail plugin shows
29
- only drafts whose `folder` frontmatter value is that folder itself or a
30
- real subfolder of it (checked through `realpath`, so symlink escapes and
31
- sibling paths are rejected).
32
- 2. **Check for drafts.** Drafts live in `~/.email/drafts/` as `<id>.md`
33
- files. Open the folder in the viewer and confirm the **Mail** plugin tab
34
- appears; the tab only appears when at least one active draft belongs to the
35
- folder, and its badge shows the draft count.
36
- 3. **Read the drafts.** Each draft is Markdown with YAML frontmatter followed
37
- by the message body. Only drafts with a non-empty `id` and an active status
38
- (`working` or `ready`) are listed — sent and canceled drafts are excluded,
39
- even though MailOS keeps them in the same directory. Read values from the
40
- files; do not invent recipients or subjects.
41
- 4. **Open the view.** `kennel serve <folder>` (or `kennel view <folder>` in a
42
- source checkout), then point at the Mail tab. Launch, port, watch, and
43
- link verification mechanics come from the `kennel-preview` skill — install
44
- it alongside this one so the full workflow works.
45
- 5. **Verify before reporting.** Confirm the server is alive
46
- (`curl -fsS "$URL/api/health"`), the printed URL serves the exact requested
47
- folder root, and confirm the specific drafts the user asked about are in
48
- the draft directory with an active status and the matching `folder` value.
49
-
50
- ## Content shape
51
-
52
- ### Draft file location
53
-
54
- - Default mail directory: `~/.email/drafts/` (the server entry accepts a
55
- `mailDir` override, intended for tests).
56
- - Each draft is `<id>.md`; ids are validated (no path separators, no `.`/`..`)
57
- before a file is read.
58
- - Only files ending in `.md` are considered.
59
-
60
- ### Draft frontmatter
61
-
62
- | Field | Meaning |
63
- | --------------------------- | ------------------------------------------------------------------------------------------------- |
64
- | `id` | Required for the draft to be listed |
65
- | `status` | `working` \| `ready` (active, listed) — sent/canceled/other are hidden |
66
- | `folder` | The folder this draft belongs to; must be the viewed folder or a real descendant (realpath check) |
67
- | `tags` | Array of strings |
68
- | `from`, `to`, `cc`, `bcc` | Sender; recipient lists (a raw string is wrapped into a single-element list) |
69
- | `in_reply_to`, `references` | Reply threading |
70
- | `subject` | Subject line |
71
- | `created_at`, `updated_at` | Timestamps; the list is sorted by `updatedAt` (falling back to `createdAt`) descending |
72
-
73
- The YAML body after the frontmatter is the message body. The list item also
74
- carries an `excerpt` (first ~280 characters of the body) and a `revision` (a
75
- sha256 of the exact draft file bytes — MailOS treats this hash as the draft
76
- revision for guarded destructive actions).
77
-
78
- ## Verification
79
-
80
- - Server alive: `curl -fsS --connect-timeout 3 --max-time 5 "$URL/api/health"`
81
- - Folder root: request `/api/documents` and confirm `root` is the requested
82
- absolute folder.
83
- - Mail availability: the Mail plugin tab is present and its badge equals the
84
- number of active drafts whose `folder` is inside the served folder.
85
- - Per-draft checks: draft ids, subjects, statuses, and `folder` values in the
86
- view match the `~/.email/drafts/*.md` source files; drafts with status
87
- `sent`/`canceled` or a `folder` outside the served root do not appear.
88
-
89
- ## Boundaries
90
-
91
- - This plugin is read-only over the draft directory: it lists and renders
92
- drafts. Composing, sending, or changing draft status is done through MailOS
93
- itself (the `mailos` CLI), not this plugin.
94
- - Do not invent draft contents, recipients, or subjects — read them from the
95
- files.
96
- - The mail view only exists inside the Kennel viewer (server + web app); the
97
- `@raggle-ai/mail` package is not a standalone CLI.
@@ -1,10 +0,0 @@
1
- import baseConfig from "@raggle-ai/eslint-config/base"
2
- import reactConfig from "@raggle-ai/eslint-config/react"
3
-
4
- export default [
5
- ...baseConfig,
6
- ...reactConfig,
7
- {
8
- ignores: ["dist/**", "**/_generated/", "vendor/**"],
9
- },
10
- ]
@@ -1,79 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/package.json",
3
- "name": "@raggle-ai/mail",
4
- "version": "0.1.0",
5
- "description": "Mail folder plugin: parse and view MailOS local email drafts for Kennel folders.",
6
- "type": "module",
7
- "license": "MIT",
8
- "prettier": "@raggle-ai/prettier-config",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/raggle-ai/kennel.git",
12
- "directory": "plugins/mail"
13
- },
14
- "publishConfig": {
15
- "registry": "https://registry.npmjs.org",
16
- "access": "public"
17
- },
18
- "files": [
19
- "src"
20
- ],
21
- "exports": {
22
- ".": "./src/index.ts",
23
- "./api": "./src/api.ts",
24
- "./client": "./src/client.tsx",
25
- "./folders": "./src/folders.ts",
26
- "./history": "./src/history.ts",
27
- "./mailos": "./src/mailos.ts",
28
- "./manifest": "./src/manifest.ts",
29
- "./node": "./src/node.ts",
30
- "./parse": "./src/parse.ts",
31
- "./server": "./src/server.ts",
32
- "./viewer": "./src/viewer.tsx"
33
- },
34
- "scripts": {
35
- "test": "bun test test/*.test.mjs",
36
- "typecheck": "tsc --noEmit",
37
- "format": "prettier . --check --cache --cache-location .cache/.prettiercache --ignore-path ../../.prettierignore",
38
- "format:fix": "prettier . --write --cache --cache-location .cache/.prettiercache --ignore-path ../../.prettierignore",
39
- "lint": "eslint . --cache --cache-location .cache/.eslintcache",
40
- "lint:fix": "eslint . --fix --cache --cache-location .cache/.eslintcache",
41
- "clean": "git clean -xdf dist node_modules .cache .turbo/cache",
42
- "pack:check": "npm pack --dry-run",
43
- "publish:public:dry-run": "npm --@raggle-ai:registry=https://registry.npmjs.org publish --dry-run --access public --registry=https://registry.npmjs.org"
44
- },
45
- "dependencies": {
46
- "@raggle-ai/folder-plugin": "workspace:*",
47
- "@raggle-ai/markdown": "workspace:*",
48
- "@raggle-ai/ui": "workspace:*",
49
- "@tanstack/react-query": "catalog:tanstack",
50
- "koffi": "^3.3.1",
51
- "lucide-react": "^0.562.0",
52
- "yaml": "^2.9.0"
53
- },
54
- "peerDependencies": {
55
- "react": ">=18.3.0"
56
- },
57
- "devDependencies": {
58
- "@eslint/compat": "catalog:eslint",
59
- "@eslint/eslintrc": "catalog:eslint",
60
- "@ianvs/prettier-plugin-sort-imports": "catalog:prettier",
61
- "@prettier/plugin-oxc": "catalog:prettier",
62
- "@raggle-ai/eslint-config": "workspace:*",
63
- "@raggle-ai/prettier-config": "workspace:*",
64
- "@raggle-ai/tsconfig": "workspace:*",
65
- "@types/node": "catalog:",
66
- "@types/react": "catalog:react19",
67
- "@types/react-dom": "catalog:react19",
68
- "eslint": "catalog:eslint",
69
- "eslint-plugin-unused-imports": "catalog:eslint",
70
- "prettier": "catalog:prettier",
71
- "prettier-plugin-classnames": "catalog:prettier",
72
- "prettier-plugin-merge": "catalog:prettier",
73
- "prettier-plugin-tailwindcss": "catalog:prettier",
74
- "react": "catalog:react19",
75
- "react-dom": "catalog:react19",
76
- "typescript": "catalog:",
77
- "typescript-eslint": "catalog:eslint"
78
- }
79
- }
@@ -1,359 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http"
2
-
3
- import type { ApiRouteHandle } from "@raggle-ai/folder-plugin/endpoint"
4
- import type { MailosService } from "@raggle-ai/mail/mailos"
5
- import {
6
- apiError,
7
- jsonResponse,
8
- parseJsonBody,
9
- runApiRoute,
10
- } from "@raggle-ai/folder-plugin/endpoint"
11
- import {
12
- deleteLocalDraftVerified,
13
- MailAttachmentError,
14
- MailDeleteError,
15
- removeDraftAttachmentVerified,
16
- } from "@raggle-ai/mail/history"
17
- import {
18
- createMailosService,
19
- MailosCommandError,
20
- MailSendFailure,
21
- } from "@raggle-ai/mail/mailos"
22
- import {
23
- defaultMailDir,
24
- isFolderWithin,
25
- listMailForFolder,
26
- readMailById,
27
- } from "@raggle-ai/mail/node"
28
-
29
- export type MailPreview = { body: string; signature: string | null }
30
-
31
- export type MailPreviewer = (options: {
32
- folder: string
33
- id: string
34
- mail: NonNullable<Awaited<ReturnType<typeof readMailById>>>
35
- }) => Promise<MailPreview>
36
-
37
- /**
38
- * Context provided by the Vite plugin. `authorizeFolder` enforces the real
39
- * viewer root (realpath + locked root), `watch` wires folder live refresh,
40
- * and `mailPreviewer`/`mailDir`/`mailos` are test seams.
41
- */
42
- export type MailApiContext = {
43
- authorizeFolder: (value: string | null) => string
44
- mailDir?: string
45
- mailos?: ReturnType<typeof createMailosService>
46
- mailPreviewer?: MailPreviewer
47
- watch: (folder: string) => void
48
- }
49
-
50
- type AuthorizedMail = {
51
- folder: string
52
- mail: NonNullable<Awaited<ReturnType<typeof readMailById>>> & {
53
- folder: string
54
- }
55
- }
56
-
57
- type Handler = ApiRouteHandle<MailApiContext>
58
-
59
- const defaultMailos = createMailosService()
60
-
61
- function defaultPreviewer(mailos: MailosService): MailPreviewer {
62
- return async ({ mail }) => ({
63
- body: mail.body,
64
- // A no_signature draft sends without any signature; do not show one.
65
- signature: mail.noSignature
66
- ? null
67
- : mail.folder
68
- ? await mailos.signature(mail.folder)
69
- : null,
70
- })
71
- }
72
-
73
- function mailDirOption(mailDir?: string) {
74
- return mailDir ?? defaultMailDir()
75
- }
76
-
77
- function requireString(value: unknown, name: string) {
78
- if (typeof value !== "string" || !value.trim()) {
79
- throw apiError("bad-request", `Missing ${name} parameter`)
80
- }
81
- return value.trim()
82
- }
83
-
84
- function requireRevision(value: unknown) {
85
- const revision = requireString(value, "revision")
86
- if (!/^[a-f0-9]{64}$/i.test(revision)) {
87
- throw apiError("bad-request", "Invalid revision parameter")
88
- }
89
- return revision.toLowerCase()
90
- }
91
-
92
- /**
93
- * Shared authorized read: the folder must be a real root/descendant of the
94
- * viewer root and the draft id must be a canonical filename with an active
95
- * MailOS status; the draft's recorded folder must really be within the
96
- * requested folder (symlink/traversal escapes collapse through realpath).
97
- */
98
- async function authorizedMail(
99
- context: MailApiContext,
100
- id: string,
101
- pathValue: string | null,
102
- ): Promise<AuthorizedMail> {
103
- const trimmed = id.trim()
104
- if (!trimmed) throw apiError("bad-request", "Missing id parameter")
105
- const folder = context.authorizeFolder(pathValue)
106
- const mail = await readMailById(trimmed, mailDirOption(context.mailDir))
107
- if (!mail) throw apiError("not-found", "Mail not found")
108
- if (!mail.folder) throw apiError("not-found", "Mail has no folder")
109
- if (!isFolderWithin(folder, mail.folder)) {
110
- throw apiError("not-found", "Mail not found for this folder")
111
- }
112
- return { folder, mail: { ...mail, folder: mail.folder } }
113
- }
114
-
115
- const listMail: Handler = async ({ context, query, response }) => {
116
- const folder = context.authorizeFolder(query.get("path"))
117
- const items = await listMailForFolder({
118
- folder,
119
- mailDir: mailDirOption(context.mailDir),
120
- })
121
- jsonResponse(response, 200, { folder, items })
122
- }
123
-
124
- const readItem: Handler = async ({ context, query, response }) => {
125
- const id = requireString(query.get("id"), "id")
126
- const { folder, mail } = await authorizedMail(context, id, query.get("path"))
127
- context.watch(folder)
128
-
129
- let preview: MailPreview
130
- try {
131
- const previewer =
132
- context.mailPreviewer ?? defaultPreviewer(context.mailos ?? defaultMailos)
133
- preview = await previewer({ id, folder, mail })
134
- } catch (error) {
135
- const detail =
136
- error instanceof Error ? error.message : "Unknown preview error"
137
- throw apiError("internal", `Mail preview failed: ${detail}`)
138
- }
139
-
140
- jsonResponse(response, 200, { mail, preview })
141
- }
142
-
143
- const resolveSender: Handler = async ({ context, query, response }) => {
144
- const id = requireString(query.get("id"), "id")
145
- const { mail } = await authorizedMail(context, id, query.get("path"))
146
- const mailos = context.mailos ?? defaultMailos
147
- const identity = await mailos.identity(mail.folder)
148
- jsonResponse(response, 200, identity)
149
- }
150
-
151
- const readConversation: Handler = async ({
152
- context,
153
- query,
154
- response,
155
- signal,
156
- }) => {
157
- const id = requireString(query.get("id"), "id")
158
- const { mail } = await authorizedMail(context, id, query.get("path"))
159
- const mailos = context.mailos ?? defaultMailos
160
- const references = [...mail.references, mail.inReplyTo].filter(
161
- (reference): reference is string => Boolean(reference),
162
- )
163
- const result = await mailos.conversation(mail.folder, references, signal)
164
- jsonResponse(response, 200, result)
165
- }
166
-
167
- /**
168
- * Convert a CLI-level failure into a structured API error without leaking
169
- * command output: a hard child timeout is a 504, an aborted child is
170
- * rethrown for the endpoint's disconnect handling, and any other non-zero
171
- * exit becomes a generic 500 with a fixed message.
172
- */
173
- function commandFailureError(error: MailosCommandError): never {
174
- if (error.timedOut || error.code === "timeout") {
175
- throw apiError("timeout", "MailOS command timed out")
176
- }
177
- if (error.code === "aborted") throw error
178
- throw apiError("failed", "MailOS command failed")
179
- }
180
-
181
- function sendFailureError(error: MailSendFailure) {
182
- switch (error.reason) {
183
- case "revision-match":
184
- return apiError("conflict", error.message)
185
- case "invalid-draft":
186
- return apiError("bad-request", error.message)
187
- case "unsupported-draft-dir":
188
- return apiError("bad-request", error.message)
189
- default:
190
- return apiError("failed", error.message)
191
- }
192
- }
193
-
194
- const runAction: Handler = async ({ body, context, response, signal }) => {
195
- const parsed = parseJsonBody(body)
196
- const action = requireString(parsed.action, "action")
197
- const id = requireString(parsed.id, "id")
198
- const revision = requireRevision(parsed.revision)
199
- if (
200
- action !== "archive" &&
201
- action !== "delete" &&
202
- action !== "remove-attachment"
203
- ) {
204
- throw apiError("bad-request", "Invalid action parameter")
205
- }
206
-
207
- const { mail } = await authorizedMail(context, id, pathFromParsed(parsed))
208
- if (mail.revision !== revision) {
209
- throw apiError("conflict", "Draft changed since it was loaded")
210
- }
211
-
212
- const mailos = context.mailos ?? defaultMailos
213
- if (action === "remove-attachment") {
214
- const attachment = requireString(parsed.attachment, "attachment")
215
- try {
216
- await removeDraftAttachmentVerified(
217
- mail,
218
- mailDirOption(context.mailDir),
219
- attachment,
220
- )
221
- } catch (error) {
222
- if (error instanceof MailAttachmentError) {
223
- switch (error.outcome) {
224
- case "missing":
225
- throw apiError("not-found", error.message)
226
- case "invalid":
227
- throw apiError("bad-request", error.message)
228
- case "changed":
229
- throw apiError("conflict", error.message)
230
- }
231
- }
232
- throw error
233
- }
234
- } else if (action === "delete") {
235
- try {
236
- await deleteLocalDraftVerified(mail, mailDirOption(context.mailDir))
237
- } catch (error) {
238
- if (error instanceof MailDeleteError) {
239
- switch (error.outcome) {
240
- case "missing":
241
- throw apiError("not-found", error.message)
242
- case "invalid":
243
- throw apiError("bad-request", error.message)
244
- case "changed":
245
- throw apiError("conflict", error.message)
246
- }
247
- }
248
- throw error
249
- }
250
- } else {
251
- try {
252
- await mailos.archive(mail.folder, mail.id, {
253
- draftDir: mailDirOption(context.mailDir),
254
- signal,
255
- })
256
- } catch (error) {
257
- if (error instanceof MailSendFailure) throw sendFailureError(error)
258
- if (error instanceof MailosCommandError) commandFailureError(error)
259
- throw error
260
- }
261
- }
262
-
263
- jsonResponse(response, 200, { action, id: mail.id, ok: true })
264
- }
265
-
266
- const runSend: Handler = async ({ body, context, response, signal }) => {
267
- const parsed = parseJsonBody(body)
268
- const id = requireString(parsed.id, "id")
269
- const dryRun = parsed.dryRun === true
270
- const { mail } = await authorizedMail(context, id, pathFromParsed(parsed))
271
- const mailos = context.mailos ?? defaultMailos
272
-
273
- if (dryRun) {
274
- const preview = await mailos
275
- .sendPreview(mail.folder, mail.id, {
276
- draftDir: mailDirOption(context.mailDir),
277
- signal,
278
- })
279
- .catch((error: unknown) => {
280
- if (error instanceof MailSendFailure) throw sendFailureError(error)
281
- if (error instanceof MailosCommandError) commandFailureError(error)
282
- throw error
283
- })
284
- jsonResponse(response, 200, preview)
285
- return
286
- }
287
-
288
- const revision = requireRevision(parsed.revision)
289
- if (mail.revision !== revision) {
290
- throw apiError("conflict", "Draft changed since it was loaded")
291
- }
292
- const result = await mailos
293
- .confirmSend(mail.folder, mail.id, revision, {
294
- draftDir: mailDirOption(context.mailDir),
295
- signal,
296
- })
297
- .catch((error: unknown) => {
298
- if (error instanceof MailSendFailure) throw sendFailureError(error)
299
- if (error instanceof MailosCommandError) commandFailureError(error)
300
- throw error
301
- })
302
- jsonResponse(response, 200, result)
303
- }
304
-
305
- function pathFromParsed(parsed: Record<string, unknown>) {
306
- return typeof parsed.path === "string" && parsed.path ? parsed.path : null
307
- }
308
-
309
- /**
310
- * All mail HTTP routes. `/api/mail`, `/api/mail/item`, `/api/mail/from` and
311
- * `/api/mail/conversation` are GET-only reads (GET never mutates);
312
- * `/api/mail/action` and `/api/mail/send` are POST mutations guarded by the
313
- * endpoint wrapper's same-origin check. Returns false for non-mail paths so
314
- * the plugin can continue its own routing.
315
- */
316
- export async function handleMailApiRoutes(
317
- request: IncomingMessage,
318
- response: ServerResponse,
319
- context: MailApiContext,
320
- ): Promise<boolean> {
321
- const url = new URL(request.url ?? "/", "http://127.0.0.1")
322
-
323
- const specByPath: Record<
324
- string,
325
- {
326
- method: "GET" | "POST"
327
- maxBodyBytes?: number
328
- timeoutMs?: number
329
- handle: Handler
330
- }
331
- > = {
332
- "/api/mail": { method: "GET", handle: listMail },
333
- "/api/mail/item": { method: "GET", handle: readItem },
334
- "/api/mail/from": { method: "GET", handle: resolveSender },
335
- "/api/mail/conversation": {
336
- method: "GET",
337
- timeoutMs: 75_000,
338
- handle: readConversation,
339
- },
340
- "/api/mail/action": {
341
- method: "POST",
342
- maxBodyBytes: 16 * 1024,
343
- timeoutMs: 60_000,
344
- handle: runAction,
345
- },
346
- "/api/mail/send": {
347
- method: "POST",
348
- maxBodyBytes: 16 * 1024,
349
- timeoutMs: 90_000,
350
- handle: runSend,
351
- },
352
- }
353
-
354
- const spec = specByPath[url.pathname]
355
- if (!spec) return false
356
-
357
- await runApiRoute(request, response, url, spec, context)
358
- return true
359
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Ambient typing for the `bun:ffi` surface flock.ts uses. Bun's runtime
3
- * module provides `dlopen`; TypeScript cannot resolve it without this, and
4
- * flock.ts keeps the import dynamic so the Node (koffi) path never touches
5
- * bun's runtime.
6
- */
7
- declare module "bun:ffi" {
8
- export function dlopen(
9
- path: string,
10
- symbols: Record<string, { args: string[]; returns: string }>,
11
- ): {
12
- symbols: Record<string, (...args: number[]) => number>
13
- }
14
- }
@@ -1,14 +0,0 @@
1
- import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
2
-
3
- import { MailDetailPage } from "./mail-detail-page"
4
- import { MailPage } from "./mail-page"
5
- import { mailManifest } from "./manifest"
6
-
7
- export function MailPlugin(props: FolderPluginViewProps) {
8
- return props.subpath ? <MailDetailPage {...props} /> : <MailPage {...props} />
9
- }
10
-
11
- export const mailClient = {
12
- ...mailManifest,
13
- component: MailPlugin,
14
- }