kennel 0.2.3 → 0.2.6

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 (274) 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 +77 -23
  9. package/dist/cli/src/site/server-plugins.d.ts +4 -9
  10. package/dist/cli/src/site/viewer.js +1 -0
  11. package/dist/cli/usage.js +1 -1
  12. package/dist/core/api-client.js +576 -10317
  13. package/dist/core/command.js +1 -0
  14. package/dist/core/core-folders.js +2313 -12088
  15. package/dist/core/init.js +5 -5
  16. package/dist/core/runtime.js +8237 -20713
  17. package/dist/package/preview.js +19617 -33352
  18. package/dist/plugins/external.d.ts +28 -2
  19. package/dist/plugins/external.js +77 -23
  20. package/dist/site/server-plugins.d.ts +4 -9
  21. package/dist/site/server-plugins.js +557 -10452
  22. package/dist/site/viewer.js +1 -0
  23. package/kernel/apps/web/package.json +2 -6
  24. package/kernel/apps/web/scripts/dev.mjs +53 -0
  25. package/kernel/apps/web/src/app.css +0 -3
  26. package/kernel/apps/web/src/external-plugins.ts +1 -1
  27. package/kernel/apps/web/src/folder-filter-icons.tsx +1 -1
  28. package/kernel/apps/web/src/folder-plugin-view.tsx +1 -1
  29. package/kernel/apps/web/src/main.tsx +0 -1
  30. package/kernel/apps/web/src/plugins.ts +1 -31
  31. package/kernel/apps/web/tsconfig.json +1 -2
  32. package/kernel/apps/web/vite.config.ts +32 -14
  33. package/kernel/packages/agents/package.json +1 -1
  34. package/kernel/packages/agents/src/agent-card.tsx +1 -1
  35. package/kernel/packages/agents/src/client.tsx +1 -1
  36. package/kernel/packages/agents/src/manifest.ts +1 -1
  37. package/kernel/packages/agents/src/server.ts +1 -1
  38. package/kernel/packages/agents/src/viewer.tsx +1 -1
  39. package/kernel/packages/api/package.json +1 -5
  40. package/kernel/packages/api/src/server/acp-live-plugin.ts +1 -1
  41. package/kernel/packages/api/src/server/api/create-agent-file.ts +2 -2
  42. package/kernel/packages/api/src/server/api/document-edit.ts +2 -2
  43. package/kernel/packages/api/src/server/api/open-path.ts +2 -2
  44. package/kernel/packages/api/src/server/folder-metadata-plugin.ts +17 -42
  45. package/kernel/packages/api/src/server/index.ts +0 -1
  46. package/kernel/packages/api/src/server/plugins.ts +52 -19
  47. package/kernel/packages/api/src/shared/viewer-route.ts +1 -1
  48. package/kernel/packages/api/test/core.test.ts +14 -26
  49. package/kernel/packages/markdown/README.md +1 -1
  50. package/kernel/packages/notes/package.json +1 -1
  51. package/kernel/packages/notes/src/client.tsx +1 -1
  52. package/kernel/packages/notes/src/manifest.ts +1 -1
  53. package/kernel/packages/notes/src/server.ts +1 -1
  54. package/kernel/packages/plugins/SKILL.md +39 -0
  55. package/kernel/{plugins/mail → packages/plugins}/package.json +53 -28
  56. package/kernel/packages/plugins/src/index.ts +52 -0
  57. package/kernel/packages/plugins/src/page.ts +17 -0
  58. package/kernel/{plugins/projects → packages/plugins}/tsconfig.json +5 -1
  59. package/kernel/packages/plugins/tsup.config.ts +25 -0
  60. package/kernel/packages/ui/src/components/content-issues.tsx +1 -1
  61. package/kernel/packages/ui/tsconfig.json +1 -1
  62. package/package.json +2 -3
  63. package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
  64. package/web/assets/app-CX20nNIa.js +2 -0
  65. package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
  66. package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
  67. package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
  68. package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
  69. package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
  70. package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
  71. package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
  72. package/web/assets/channel-X7WHyxxT.js +1 -0
  73. package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
  74. package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
  75. package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
  76. package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
  77. package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
  78. package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
  79. package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
  80. package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
  81. package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
  82. package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
  83. package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
  84. package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
  85. package/web/assets/client-7OWw5kW2.js +6 -0
  86. package/web/assets/client-DYj_td4Q.js +17 -0
  87. package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
  88. package/web/assets/command-zkzH4hVz.js +38 -0
  89. package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
  90. package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
  91. package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
  92. package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
  93. package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
  94. package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
  95. package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
  96. package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
  97. package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
  98. package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
  99. package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
  100. package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
  101. package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
  102. package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
  103. package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
  104. package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
  105. package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
  106. package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
  107. package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
  108. package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
  109. package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
  110. package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
  111. package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
  112. package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
  113. package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
  114. package/web/assets/index-CegvVQ1C.js +1 -0
  115. package/web/assets/index-Dwhf8F_U.css +1 -0
  116. package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
  117. package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
  118. package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
  119. package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
  120. package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
  121. package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
  122. package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
  123. package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
  124. package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
  125. package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
  126. package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
  127. package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
  128. package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
  129. package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
  130. package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
  131. package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
  132. package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
  133. package/web/assets/select-CtXC_PLC.js +30 -0
  134. package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
  135. package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
  136. package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
  137. package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
  138. package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
  139. package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
  140. package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
  141. package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
  142. package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
  143. package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
  144. package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
  145. package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
  146. package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
  147. package/web/index.html +2 -2
  148. package/kernel/packages/api/test/partial-loading.test.ts +0 -43
  149. package/kernel/packages/folder-plugin/package.json +0 -50
  150. package/kernel/packages/folder-plugin/src/index.ts +0 -15
  151. package/kernel/packages/folder-plugin/tsconfig.json +0 -17
  152. package/kernel/plugins/calendar/SKILL.md +0 -173
  153. package/kernel/plugins/calendar/eslint.config.js +0 -10
  154. package/kernel/plugins/calendar/package.json +0 -73
  155. package/kernel/plugins/calendar/schema.json +0 -137
  156. package/kernel/plugins/calendar/src/calendar.tsx +0 -328
  157. package/kernel/plugins/calendar/src/client.tsx +0 -139
  158. package/kernel/plugins/calendar/src/context.ts +0 -48
  159. package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
  160. package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
  161. package/kernel/plugins/calendar/src/index.ts +0 -4
  162. package/kernel/plugins/calendar/src/manifest.ts +0 -7
  163. package/kernel/plugins/calendar/src/parse.ts +0 -610
  164. package/kernel/plugins/calendar/src/server.ts +0 -160
  165. package/kernel/plugins/calendar/src/utils.ts +0 -80
  166. package/kernel/plugins/calendar/src/views.tsx +0 -418
  167. package/kernel/plugins/calendar/test/context.test.mjs +0 -60
  168. package/kernel/plugins/calendar/test/package.test.mjs +0 -43
  169. package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
  170. package/kernel/plugins/calendar/test/server.test.mjs +0 -126
  171. package/kernel/plugins/calendar/tsconfig.json +0 -17
  172. package/kernel/plugins/mail/SKILL.md +0 -97
  173. package/kernel/plugins/mail/eslint.config.js +0 -10
  174. package/kernel/plugins/mail/src/api.ts +0 -359
  175. package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
  176. package/kernel/plugins/mail/src/client.tsx +0 -14
  177. package/kernel/plugins/mail/src/flock.ts +0 -137
  178. package/kernel/plugins/mail/src/folders.ts +0 -37
  179. package/kernel/plugins/mail/src/history.ts +0 -485
  180. package/kernel/plugins/mail/src/index.ts +0 -3
  181. package/kernel/plugins/mail/src/mail-card.tsx +0 -123
  182. package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
  183. package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
  184. package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
  185. package/kernel/plugins/mail/src/mail-page.tsx +0 -109
  186. package/kernel/plugins/mail/src/mail-request.ts +0 -78
  187. package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
  188. package/kernel/plugins/mail/src/mailos.ts +0 -723
  189. package/kernel/plugins/mail/src/manifest.ts +0 -7
  190. package/kernel/plugins/mail/src/node.ts +0 -171
  191. package/kernel/plugins/mail/src/parse.ts +0 -139
  192. package/kernel/plugins/mail/src/server.ts +0 -125
  193. package/kernel/plugins/mail/src/types.ts +0 -10
  194. package/kernel/plugins/mail/src/viewer.tsx +0 -147
  195. package/kernel/plugins/mail/test/flock.test.mjs +0 -156
  196. package/kernel/plugins/mail/test/folders.test.mjs +0 -138
  197. package/kernel/plugins/mail/test/history.test.mjs +0 -460
  198. package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
  199. package/kernel/plugins/mail/test/node.test.mjs +0 -207
  200. package/kernel/plugins/mail/test/package.test.mjs +0 -54
  201. package/kernel/plugins/mail/test/parse.test.mjs +0 -32
  202. package/kernel/plugins/mail/test/server.test.mjs +0 -116
  203. package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
  204. package/kernel/plugins/mail/tsconfig.json +0 -17
  205. package/kernel/plugins/projects/SKILL.md +0 -73
  206. package/kernel/plugins/projects/eslint.config.js +0 -10
  207. package/kernel/plugins/projects/package.json +0 -70
  208. package/kernel/plugins/projects/src/client.tsx +0 -17
  209. package/kernel/plugins/projects/src/data/model.ts +0 -129
  210. package/kernel/plugins/projects/src/index.ts +0 -17
  211. package/kernel/plugins/projects/src/manifest.ts +0 -7
  212. package/kernel/plugins/projects/src/project-context.ts +0 -41
  213. package/kernel/plugins/projects/src/server.ts +0 -276
  214. package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
  215. package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
  216. package/kernel/plugins/projects/test/package.test.mjs +0 -51
  217. package/kernel/plugins/tasks/SKILL.md +0 -341
  218. package/kernel/plugins/tasks/eslint.config.js +0 -10
  219. package/kernel/plugins/tasks/package.json +0 -74
  220. package/kernel/plugins/tasks/schema.json +0 -939
  221. package/kernel/plugins/tasks/src/api.ts +0 -212
  222. package/kernel/plugins/tasks/src/client.tsx +0 -23
  223. package/kernel/plugins/tasks/src/data/parse.ts +0 -544
  224. package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
  225. package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
  226. package/kernel/plugins/tasks/src/index.ts +0 -34
  227. package/kernel/plugins/tasks/src/manifest.ts +0 -7
  228. package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
  229. package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
  230. package/kernel/plugins/tasks/src/server.ts +0 -447
  231. package/kernel/plugins/tasks/src/styles.css +0 -9
  232. package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
  233. package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
  234. package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
  235. package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
  236. package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
  237. package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
  238. package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
  239. package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
  240. package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
  241. package/kernel/plugins/tasks/test/api.test.mjs +0 -99
  242. package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
  243. package/kernel/plugins/tasks/test/package.test.mjs +0 -54
  244. package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
  245. package/kernel/plugins/tasks/test/server.test.mjs +0 -386
  246. package/kernel/plugins/tasks/tsconfig.json +0 -17
  247. package/web/assets/app-DoGr2Zi7.js +0 -2
  248. package/web/assets/channel-uMEjV8qc.js +0 -1
  249. package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
  250. package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
  251. package/web/assets/client-C6Nxdfif.js +0 -2
  252. package/web/assets/client-CXYfBko5.js +0 -17
  253. package/web/assets/client-CoKtOq-X.js +0 -7
  254. package/web/assets/client-DyRQz5RU.js +0 -53
  255. package/web/assets/client-OY6HUzuD.js +0 -98
  256. package/web/assets/client-YOYWZIFi.js +0 -34
  257. package/web/assets/command-BugZBGXE.js +0 -38
  258. package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
  259. package/web/assets/index-BaRnbYRw.js +0 -1
  260. package/web/assets/index-BexmLxIP.css +0 -1
  261. package/web/assets/index-DDirZMgx.js +0 -1
  262. package/web/assets/note-card-DBfX6gW1.js +0 -6
  263. package/web/assets/select-D8cqNgcp.js +0 -30
  264. package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
  265. package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
  266. package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
  267. /package/kernel/packages/{folder-plugin → plugins}/eslint.config.js +0 -0
  268. /package/kernel/packages/{folder-plugin → plugins}/src/client.ts +0 -0
  269. /package/kernel/packages/{folder-plugin → plugins}/src/endpoint.ts +0 -0
  270. /package/kernel/packages/{folder-plugin → plugins}/src/server.ts +0 -0
  271. /package/kernel/packages/{folder-plugin → plugins}/src/subpath.ts +0 -0
  272. /package/kernel/packages/{folder-plugin → plugins}/src/types.ts +0 -0
  273. /package/kernel/packages/{folder-plugin → plugins}/src/validation.ts +0 -0
  274. /package/kernel/packages/{folder-plugin → plugins}/test/contract.test.mjs +0 -0
@@ -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,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
- }
@@ -1,137 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/triple-slash-reference -- ambient bun:ffi types for out-of-package consumers (packages/api compiles this file)
2
- /// <reference path="./bun-ffi.d.ts" />
3
- import { closeSync, constants, openSync } from "node:fs"
4
- import { mkdir } from "node:fs/promises"
5
- import { dirname, resolve } from "node:path"
6
-
7
- /**
8
- * Real POSIX advisory lock via `flock(2)` on MailOS-compatible lock files.
9
- *
10
- * MailOS (`emailos/internal/core/drafts.go` `withLocalDraftLock`) serializes
11
- * draft mutations with an exclusive `flock` on `<draftDir>/.locks/<base>.lock`
12
- * opened `O_CREATE|O_RDWR`. Kennel takes the *same* lock so destructive
13
- * actions (delete/archive critical sections) coordinate with the MailOS
14
- * binary itself, not just with other Kennel processes.
15
- *
16
- * Portability: `flock(2)` is reached through two supported FFI backends —
17
- * `bun:ffi` when the process runs on Bun (the test runner), and the `koffi`
18
- * N-API binding when it runs on Node (the Vite dev server / build). Both call
19
- * the exact same libc symbol on the exact same lock files, so coordination
20
- * holds across runtimes and against the Go MailOS binary. The wait is
21
- * bounded so an abandoned holder cannot hang a server request forever.
22
- */
23
- export class FlockTimeoutError extends Error {
24
- readonly lockPath: string
25
-
26
- constructor(lockPath: string, timeoutMs: number) {
27
- super(
28
- `Timed out after ${timeoutMs}ms waiting for the draft lock ${resolve(lockPath)}`,
29
- )
30
- this.name = "FlockTimeoutError"
31
- this.lockPath = resolve(lockPath)
32
- }
33
- }
34
-
35
- const LOCK_EX = 2
36
- const LOCK_NB = 4
37
- const LOCK_UN = 8
38
- const EINTR = 4
39
- const EWOULDBLOCK_DARWIN = 35
40
- const EWOULDBLOCK_LINUX = 11
41
-
42
- type FlockFn = (fd: number, operation: number) => number
43
-
44
- type FlockBackend = {
45
- flock: FlockFn
46
- /** Returns the last libc errno; only used on Node (koffi exposes it). */
47
- errno: () => number
48
- }
49
-
50
- function isBunRuntime() {
51
- return (
52
- typeof process !== "undefined" &&
53
- typeof process.versions !== "undefined" &&
54
- "bun" in process.versions
55
- )
56
- }
57
-
58
- const libcPath =
59
- process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6"
60
-
61
- let backendPromise: Promise<FlockBackend> | null = null
62
-
63
- async function loadBackend(): Promise<FlockBackend> {
64
- backendPromise ??= (async () => {
65
- if (isBunRuntime()) {
66
- // bun:ffi does not surface errno; a non-zero flock result on the just
67
- // opened descriptor is treated as "held elsewhere" (would-block /
68
- // EINTR) and retried until the bounded deadline, which is the only
69
- // realistic outcome for a valid descriptor.
70
- const { dlopen } = await import("bun:ffi")
71
- const libc = dlopen(libcPath, {
72
- flock: { args: ["int", "int"], returns: "int" },
73
- })
74
- return { flock: libc.symbols.flock as FlockFn, errno: () => 0 }
75
- }
76
- // Node: koffi exposes libc errno so would-block/EINTR can be told
77
- // apart from a real locking error.
78
- const koffi = (await import("koffi")).default
79
- const libc = koffi.load(libcPath)
80
- return {
81
- flock: libc.func("int flock(int fd, int operation)"),
82
- errno: koffi.errno,
83
- }
84
- })()
85
- return backendPromise
86
- }
87
-
88
- const wouldBlockErrno =
89
- process.platform === "darwin" ? EWOULDBLOCK_DARWIN : EWOULDBLOCK_LINUX
90
-
91
- function sleep(ms: number) {
92
- return new Promise((resolveSleep) => {
93
- setTimeout(resolveSleep, ms)
94
- })
95
- }
96
-
97
- /**
98
- * Run `action` while holding an exclusive `flock` on `lockPath`. The file is
99
- * opened `O_RDWR|O_CREAT` (mode `0600`) exactly like MailOS; the lock is
100
- * released (and the descriptor closed) before `withFlock` resolves, so a
101
- * crash never leaves a stale advisory lock behind.
102
- */
103
- export async function withFlock<T>(
104
- lockPath: string,
105
- timeoutMs: number,
106
- action: () => Promise<T>,
107
- ): Promise<T> {
108
- const resolved = resolve(lockPath)
109
- // MailOS creates the lock directory with `os.MkdirAll(lockDir, 0o700)`.
110
- await mkdir(dirname(resolved), { mode: 0o700, recursive: true }).catch(
111
- () => undefined,
112
- )
113
- const { flock, errno } = await loadBackend()
114
- const fd = openSync(resolved, constants.O_RDWR | constants.O_CREAT, 0o600)
115
- try {
116
- const deadline = Date.now() + timeoutMs
117
- for (;;) {
118
- if (flock(fd, LOCK_EX | LOCK_NB) === 0) break
119
- const code = errno()
120
- if (code !== 0 && code !== EINTR && code !== wouldBlockErrno) {
121
- throw new Error(`flock failed on ${resolved} (errno ${code})`)
122
- }
123
- const remaining = deadline - Date.now()
124
- if (remaining <= 0) {
125
- throw new FlockTimeoutError(resolved, timeoutMs)
126
- }
127
- await sleep(Math.min(50, remaining))
128
- }
129
- return await action()
130
- } finally {
131
- try {
132
- flock(fd, LOCK_UN)
133
- } finally {
134
- closeSync(fd)
135
- }
136
- }
137
- }
@@ -1,37 +0,0 @@
1
- import type { FolderFilterChoice } from "@raggle-ai/ui/lib/folder-filter"
2
- import {
3
- folderFilterChoices,
4
- folderFilterLabel,
5
- } from "@raggle-ai/ui/lib/folder-filter"
6
-
7
- export type MailFolderChoice = FolderFilterChoice
8
-
9
- /**
10
- * Display text for a draft's owning folder relative to the viewed root:
11
- * basename for root-owned drafts ("example-workspace"), relative path for
12
- * descendants ("clients/client-one"). Null when the draft has no folder or it
13
- * does not live under the viewed root.
14
- *
15
- * Delegates to the shared folder-filter lib.
16
- */
17
- export function folderLabel(
18
- folder: string | null,
19
- rootFolder: string,
20
- ): string | null {
21
- return folderFilterLabel(folder, rootFolder)
22
- }
23
-
24
- /**
25
- * Exact owning-folder choices with per-draft counts for filtering the mail
26
- * list. Keyed by the full folder path so duplicate leaf names stay
27
- * distinct; ordered with the root folder first, then descendants
28
- * alphabetically by relative path.
29
- *
30
- * Delegates to the shared folder-filter lib.
31
- */
32
- export function folderChoices(
33
- items: readonly { folder: string | null }[],
34
- rootFolder: string,
35
- ): MailFolderChoice[] {
36
- return folderFilterChoices(items, rootFolder, (item) => item.folder)
37
- }