kennel 0.2.2 → 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 +29 -3
  8. package/dist/cli/src/plugins/external.js +78 -24
  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 +29 -3
  17. package/dist/plugins/external.js +78 -24
  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
@@ -55493,8 +55493,8 @@ var require_dist4 = __commonJS((exports) => {
55493
55493
  import {
55494
55494
  createServer
55495
55495
  } from "node:http";
55496
- import { readFile as readFile15, realpath as realpath8, stat as stat10 } from "node:fs/promises";
55497
- import { extname as extname7, isAbsolute as isAbsolute10, relative as relative15, resolve as resolve19 } from "node:path";
55496
+ import { readFile as readFile10, realpath as realpath6, stat as stat9 } from "node:fs/promises";
55497
+ import { extname as extname7, isAbsolute as isAbsolute7, relative as relative11, resolve as resolve13 } from "node:path";
55498
55498
 
55499
55499
  // packages/api/src/server/watcher.ts
55500
55500
  import { existsSync, statSync, watch } from "node:fs";
@@ -55540,7 +55540,7 @@ function createWatcher(onChange) {
55540
55540
  }
55541
55541
 
55542
55542
  // packages/api/src/server/folder-metadata-plugin.ts
55543
- import { createHash as createHash7 } from "node:crypto";
55543
+ import { createHash as createHash5 } from "node:crypto";
55544
55544
  // node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
55545
55545
  var exports_external = {};
55546
55546
  __export(exports_external, {
@@ -70192,17 +70192,17 @@ function sendContract(response, url2, body, headers = {}) {
70192
70192
  }
70193
70193
 
70194
70194
  // packages/api/src/server/folder-metadata-plugin.ts
70195
- import { realpathSync as realpathSync3 } from "node:fs";
70196
- import { readdir as readdir9, readFile as readFile14, stat as stat9 } from "node:fs/promises";
70197
- import { homedir as homedir6 } from "node:os";
70195
+ import { realpathSync as realpathSync2 } from "node:fs";
70196
+ import { readdir as readdir5, readFile as readFile9, stat as stat8 } from "node:fs/promises";
70197
+ import { homedir as homedir4 } from "node:os";
70198
70198
  import {
70199
- basename as basename9,
70200
- dirname as dirname16,
70199
+ basename as basename7,
70200
+ dirname as dirname10,
70201
70201
  extname as extname6,
70202
- isAbsolute as isAbsolute9,
70203
- relative as relative14,
70204
- resolve as resolve18,
70205
- sep as sep12
70202
+ isAbsolute as isAbsolute6,
70203
+ relative as relative10,
70204
+ resolve as resolve12,
70205
+ sep as sep7
70206
70206
  } from "node:path";
70207
70207
 
70208
70208
  // node_modules/.bun/@raggle-ai+barkdown@0.6.1+22117d1132b05fb8/node_modules/@raggle-ai/barkdown/dist/react.js
@@ -83665,7 +83665,7 @@ async function readFolder(root) {
83665
83665
  };
83666
83666
  }
83667
83667
  // packages/api/src/server/folder-metadata-plugin.ts
83668
- import { execFile as execFile5 } from "node:child_process";
83668
+ import { execFile as execFile4 } from "node:child_process";
83669
83669
 
83670
83670
  // packages/folder-plugin/src/endpoint.ts
83671
83671
  var statusByCode = {
@@ -83832,7 +83832,7 @@ async function runApiRoute(request, response, url2, spec, context) {
83832
83832
  }
83833
83833
 
83834
83834
  // packages/api/src/server/folder-metadata-plugin.ts
83835
- var import_yaml5 = __toESM(require_dist(), 1);
83835
+ var import_yaml4 = __toESM(require_dist(), 1);
83836
83836
 
83837
83837
  // packages/folder-plugin/src/server.ts
83838
83838
  class FolderPluginFailure extends Error {
@@ -84793,1819 +84793,6 @@ function createSchemaCollectionServer(host, options = {}) {
84793
84793
  // packages/api/src/server/folder-metadata-plugin.ts
84794
84794
  var import_local3 = __toESM(require_dist4(), 1);
84795
84795
 
84796
- // plugins/mail/src/history.ts
84797
- import { createHash as createHash4 } from "node:crypto";
84798
- import {
84799
- lstat,
84800
- mkdir as mkdir3,
84801
- readFile as readFile4,
84802
- realpath as realpath3,
84803
- rename as rename2,
84804
- unlink as unlink2,
84805
- writeFile as writeFile2
84806
- } from "node:fs/promises";
84807
- import { dirname as dirname6, isAbsolute as isAbsolute4, relative as relative6, resolve as resolve7, sep as sep4 } from "node:path";
84808
-
84809
- // plugins/mail/src/flock.ts
84810
- import { closeSync, constants as constants2, openSync } from "node:fs";
84811
- import { mkdir as mkdir2 } from "node:fs/promises";
84812
- import { dirname as dirname5, resolve as resolve6 } from "node:path";
84813
-
84814
- class FlockTimeoutError extends Error {
84815
- lockPath;
84816
- constructor(lockPath, timeoutMs) {
84817
- super(`Timed out after ${timeoutMs}ms waiting for the draft lock ${resolve6(lockPath)}`);
84818
- this.name = "FlockTimeoutError";
84819
- this.lockPath = resolve6(lockPath);
84820
- }
84821
- }
84822
- var LOCK_EX = 2;
84823
- var LOCK_NB = 4;
84824
- var LOCK_UN = 8;
84825
- var EINTR = 4;
84826
- var EWOULDBLOCK_DARWIN = 35;
84827
- var EWOULDBLOCK_LINUX = 11;
84828
- function isBunRuntime() {
84829
- return typeof process !== "undefined" && typeof process.versions !== "undefined" && "bun" in process.versions;
84830
- }
84831
- var libcPath = process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6";
84832
- var backendPromise = null;
84833
- async function loadBackend() {
84834
- backendPromise ??= (async () => {
84835
- if (isBunRuntime()) {
84836
- const { dlopen } = await import("bun:ffi");
84837
- const libc2 = dlopen(libcPath, {
84838
- flock: { args: ["int", "int"], returns: "int" }
84839
- });
84840
- return { flock: libc2.symbols.flock, errno: () => 0 };
84841
- }
84842
- const koffi = (await import("koffi")).default;
84843
- const libc = koffi.load(libcPath);
84844
- return {
84845
- flock: libc.func("int flock(int fd, int operation)"),
84846
- errno: koffi.errno
84847
- };
84848
- })();
84849
- return backendPromise;
84850
- }
84851
- var wouldBlockErrno = process.platform === "darwin" ? EWOULDBLOCK_DARWIN : EWOULDBLOCK_LINUX;
84852
- function sleep(ms) {
84853
- return new Promise((resolveSleep) => {
84854
- setTimeout(resolveSleep, ms);
84855
- });
84856
- }
84857
- async function withFlock(lockPath, timeoutMs, action) {
84858
- const resolved = resolve6(lockPath);
84859
- await mkdir2(dirname5(resolved), { mode: 448, recursive: true }).catch(() => {
84860
- return;
84861
- });
84862
- const { flock, errno } = await loadBackend();
84863
- const fd = openSync(resolved, constants2.O_RDWR | constants2.O_CREAT, 384);
84864
- try {
84865
- const deadline = Date.now() + timeoutMs;
84866
- for (;; ) {
84867
- if (flock(fd, LOCK_EX | LOCK_NB) === 0)
84868
- break;
84869
- const code = errno();
84870
- if (code !== 0 && code !== EINTR && code !== wouldBlockErrno) {
84871
- throw new Error(`flock failed on ${resolved} (errno ${code})`);
84872
- }
84873
- const remaining = deadline - Date.now();
84874
- if (remaining <= 0) {
84875
- throw new FlockTimeoutError(resolved, timeoutMs);
84876
- }
84877
- await sleep(Math.min(50, remaining));
84878
- }
84879
- return await action();
84880
- } finally {
84881
- try {
84882
- flock(fd, LOCK_UN);
84883
- } finally {
84884
- closeSync(fd);
84885
- }
84886
- }
84887
- }
84888
-
84889
- // plugins/mail/src/parse.ts
84890
- var import_yaml3 = __toESM(require_dist(), 1);
84891
- function stringOrNull(value) {
84892
- return typeof value === "string" && value.trim() ? value : null;
84893
- }
84894
- function boolValue(value) {
84895
- return value === true || value === "true";
84896
- }
84897
- function stringList(value) {
84898
- if (!value)
84899
- return [];
84900
- if (Array.isArray(value))
84901
- return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
84902
- if (typeof value === "string")
84903
- return value.trim() ? [value.trim()] : [];
84904
- return [];
84905
- }
84906
- function parseMailMarkdown(content) {
84907
- if (!content.startsWith(`---
84908
- `)) {
84909
- return {
84910
- id: null,
84911
- status: null,
84912
- tags: [],
84913
- folder: null,
84914
- attachments: [],
84915
- from: null,
84916
- inReplyTo: null,
84917
- references: [],
84918
- to: [],
84919
- cc: [],
84920
- bcc: [],
84921
- subject: null,
84922
- createdAt: null,
84923
- updatedAt: null,
84924
- noSignature: false,
84925
- body: content
84926
- };
84927
- }
84928
- const end = content.indexOf(`
84929
- ---
84930
- `);
84931
- if (end < 0) {
84932
- return {
84933
- id: null,
84934
- status: null,
84935
- tags: [],
84936
- folder: null,
84937
- attachments: [],
84938
- from: null,
84939
- inReplyTo: null,
84940
- references: [],
84941
- to: [],
84942
- cc: [],
84943
- bcc: [],
84944
- subject: null,
84945
- createdAt: null,
84946
- updatedAt: null,
84947
- noSignature: false,
84948
- body: content
84949
- };
84950
- }
84951
- const frontmatterRaw = content.slice(4, end);
84952
- const body = content.slice(end + `
84953
- ---
84954
- `.length).replace(/^\n+/, "");
84955
- const frontmatterValue = import_yaml3.parse(frontmatterRaw);
84956
- const frontmatter2 = frontmatterValue && typeof frontmatterValue === "object" && !Array.isArray(frontmatterValue) ? frontmatterValue : {};
84957
- return {
84958
- id: stringOrNull(frontmatter2.id),
84959
- status: stringOrNull(frontmatter2.status),
84960
- tags: stringList(frontmatter2.tags),
84961
- folder: stringOrNull(frontmatter2.folder),
84962
- attachments: stringList(frontmatter2.file),
84963
- from: stringOrNull(frontmatter2.from),
84964
- inReplyTo: stringOrNull(frontmatter2.in_reply_to),
84965
- references: stringList(frontmatter2.references),
84966
- to: stringList(frontmatter2.to),
84967
- cc: stringList(frontmatter2.cc),
84968
- bcc: stringList(frontmatter2.bcc),
84969
- subject: stringOrNull(frontmatter2.subject),
84970
- createdAt: stringOrNull(frontmatter2.created_at),
84971
- updatedAt: stringOrNull(frontmatter2.updated_at),
84972
- noSignature: boolValue(frontmatter2.no_signature),
84973
- body
84974
- };
84975
- }
84976
- function mailExcerpt(body, maxChars = 280) {
84977
- const normalized = body.trim().replace(/\r\n/g, `
84978
- `).replace(/\n{3,}/g, `
84979
-
84980
- `);
84981
- if (normalized.length <= maxChars)
84982
- return normalized;
84983
- return `${normalized.slice(0, maxChars).trimEnd()}…`;
84984
- }
84985
-
84986
- // plugins/mail/src/history.ts
84987
- class MailDeleteError extends Error {
84988
- outcome;
84989
- constructor(outcome, message) {
84990
- super(message);
84991
- this.name = "MailDeleteError";
84992
- this.outcome = outcome;
84993
- }
84994
- }
84995
- var deleteLockTimeoutMs = 30000;
84996
- function validDraftId(id) {
84997
- const trimmed = id.trim();
84998
- return Boolean(trimmed) && trimmed !== "." && trimmed !== ".." && !/[\\/]/.test(trimmed);
84999
- }
85000
- function draftRevision(content) {
85001
- return createHash4("sha256").update(content).digest("hex");
85002
- }
85003
- async function ensureRealDirChain(anchor, childPath) {
85004
- const rel = relative6(anchor, childPath);
85005
- if (!rel || rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel)) {
85006
- throw new MailDeleteError("invalid", "Lock/history path escapes the mail folder");
85007
- }
85008
- let current = anchor;
85009
- for (const segment of rel.split(sep4)) {
85010
- if (!segment)
85011
- continue;
85012
- current = resolve7(current, segment);
85013
- let info = await lstat(current).catch(() => {
85014
- return;
85015
- });
85016
- if (!info) {
85017
- await mkdir3(current, { mode: 448 }).catch(() => {
85018
- return;
85019
- });
85020
- info = await lstat(current).catch(() => {
85021
- return;
85022
- });
85023
- if (!info || info.isSymbolicLink() || !info.isDirectory()) {
85024
- throw new MailDeleteError("invalid", "Could not create a real directory for the draft lock/history");
85025
- }
85026
- continue;
85027
- }
85028
- if (info.isSymbolicLink() || !info.isDirectory()) {
85029
- throw new MailDeleteError("invalid", "Draft lock/history path must use real directories (no symlinked components)");
85030
- }
85031
- }
85032
- }
85033
- async function deleteLocalDraftVerified(mail, mailDir) {
85034
- if (!validDraftId(mail.id)) {
85035
- throw new MailDeleteError("invalid", "Invalid mail id");
85036
- }
85037
- const mailDirReal = await realpath3(resolve7(mailDir));
85038
- const target = resolve7(mail.filePath);
85039
- const targetDir = await realpath3(dirname6(target)).catch(() => null);
85040
- if (targetDir !== mailDirReal) {
85041
- throw new MailDeleteError("invalid", "Draft path is outside the mail folder");
85042
- }
85043
- const expected = resolve7(mailDirReal, `${mail.id.trim()}.md`);
85044
- const targetReal = await realpath3(target).catch(() => null);
85045
- if (targetReal === null) {
85046
- throw new MailDeleteError("missing", "Draft no longer exists");
85047
- }
85048
- if (targetReal !== expected) {
85049
- throw new MailDeleteError("invalid", "Draft path is not a direct file in the mail folder");
85050
- }
85051
- if (!/^[a-f0-9]{64}$/i.test(mail.revision)) {
85052
- throw new MailDeleteError("invalid", "Invalid draft revision");
85053
- }
85054
- const locksDir = resolve7(mailDirReal, ".locks");
85055
- await ensureRealDirChain(mailDirReal, locksDir);
85056
- const lockPath = resolve7(locksDir, `${mail.id.trim()}.md.lock`);
85057
- try {
85058
- return await withFlock(lockPath, deleteLockTimeoutMs, async () => {
85059
- const content = await readFile4(target, "utf8").catch(() => null);
85060
- if (content === null) {
85061
- throw new MailDeleteError("missing", "Draft no longer exists");
85062
- }
85063
- if (draftRevision(content) !== mail.revision.toLowerCase()) {
85064
- throw new MailDeleteError("changed", "Draft changed since it was loaded; refusing to delete newer work");
85065
- }
85066
- const historyDir = resolve7(mailDirReal, "history", mail.id);
85067
- await ensureRealDirChain(mailDirReal, historyDir);
85068
- const historyPath = resolve7(historyDir, `${mail.revision}.md`);
85069
- const existingLstat = await lstat(historyPath).catch(() => {
85070
- return;
85071
- });
85072
- if (existingLstat?.isSymbolicLink()) {
85073
- throw new MailDeleteError("invalid", "History snapshot path is a symlink; refusing to touch it");
85074
- }
85075
- const existing = existingLstat ?? undefined;
85076
- if (existing?.isFile()) {
85077
- const existingContent = await readFile4(historyPath, "utf8").catch(() => null);
85078
- if (existingContent === null || draftRevision(existingContent) !== mail.revision.toLowerCase()) {
85079
- throw new MailDeleteError("changed", "Existing draft history does not match this revision; refusing to delete");
85080
- }
85081
- } else if (existing) {
85082
- throw new MailDeleteError("invalid", "History snapshot path is not a regular file");
85083
- } else {
85084
- const tempPath = resolve7(historyDir, `${mail.revision}.md.tmp`);
85085
- for (let attempt = 0;; attempt += 1) {
85086
- try {
85087
- await writeFile2(tempPath, content, { flag: "wx", mode: 384 });
85088
- break;
85089
- } catch (error51) {
85090
- if (error51.code !== "EEXIST")
85091
- throw error51;
85092
- const tempInfo = await lstat(tempPath).catch(() => {
85093
- return;
85094
- });
85095
- if (attempt === 0 && tempInfo?.isFile()) {
85096
- await unlink2(tempPath);
85097
- continue;
85098
- }
85099
- throw new MailDeleteError("invalid", "Temp snapshot path is not a plain file; refusing to write through it");
85100
- }
85101
- }
85102
- const written = await readFile4(tempPath, "utf8").catch(() => null);
85103
- if (written !== content || draftRevision(written ?? "") !== mail.revision.toLowerCase()) {
85104
- throw new MailDeleteError("changed", "Draft changed while snapshotting; refusing to delete");
85105
- }
85106
- await rename2(tempPath, historyPath);
85107
- }
85108
- const current = await readFile4(target, "utf8").catch(() => null);
85109
- if (current === null) {
85110
- throw new MailDeleteError("missing", "Draft no longer exists");
85111
- }
85112
- if (draftRevision(current) !== mail.revision.toLowerCase()) {
85113
- throw new MailDeleteError("changed", "Draft changed since it was loaded; refusing to delete newer work");
85114
- }
85115
- const finalReal = await realpath3(target).catch(() => null);
85116
- if (finalReal !== expected) {
85117
- throw new MailDeleteError("invalid", "Draft path changed identity before deletion; refusing to unlink");
85118
- }
85119
- await unlink2(target);
85120
- return "deleted";
85121
- });
85122
- } catch (error51) {
85123
- if (error51 instanceof FlockTimeoutError) {
85124
- throw new MailDeleteError("changed", "Draft is locked by another process (MailOS or another delete); try again");
85125
- }
85126
- throw error51;
85127
- }
85128
- }
85129
-
85130
- class MailAttachmentError extends Error {
85131
- outcome;
85132
- constructor(outcome, message) {
85133
- super(message);
85134
- this.name = "MailAttachmentError";
85135
- this.outcome = outcome;
85136
- }
85137
- }
85138
- function rewriteFileFrontmatter(frontmatterRaw, remaining, updatedAt) {
85139
- const lines = frontmatterRaw.split(`
85140
- `);
85141
- const fileIndex = lines.findIndex((line) => /^file:/.test(line));
85142
- if (fileIndex < 0)
85143
- return null;
85144
- let end = fileIndex + 1;
85145
- if ((lines[fileIndex] ?? "").trim() === "file:") {
85146
- while (end < lines.length) {
85147
- if (!/^\s+- /.test(lines[end] ?? ""))
85148
- break;
85149
- end += 1;
85150
- }
85151
- }
85152
- const fileBlock = remaining.length ? ["file:", ...remaining.map((path) => ` - ${JSON.stringify(path)}`)] : ["file: []"];
85153
- lines.splice(fileIndex, end - fileIndex, ...fileBlock);
85154
- const updatedIndex = lines.findIndex((line) => /^updated_at:/.test(line));
85155
- if (updatedIndex >= 0) {
85156
- lines[updatedIndex] = `updated_at: ${JSON.stringify(updatedAt)}`;
85157
- }
85158
- return lines.join(`
85159
- `);
85160
- }
85161
- async function removeDraftAttachmentVerified(mail, mailDir, attachment) {
85162
- if (!validDraftId(mail.id)) {
85163
- throw new MailAttachmentError("invalid", "Invalid mail id");
85164
- }
85165
- const mailDirReal = await realpath3(resolve7(mailDir));
85166
- const target = resolve7(mail.filePath);
85167
- const targetDir = await realpath3(dirname6(target)).catch(() => null);
85168
- if (targetDir !== mailDirReal) {
85169
- throw new MailAttachmentError("invalid", "Draft path is outside the mail folder");
85170
- }
85171
- const expected = resolve7(mailDirReal, `${mail.id.trim()}.md`);
85172
- const targetReal = await realpath3(target).catch(() => null);
85173
- if (targetReal === null) {
85174
- throw new MailAttachmentError("missing", "Draft no longer exists");
85175
- }
85176
- if (targetReal !== expected) {
85177
- throw new MailAttachmentError("invalid", "Draft path is not a direct file in the mail folder");
85178
- }
85179
- if (!/^[a-f0-9]{64}$/i.test(mail.revision)) {
85180
- throw new MailAttachmentError("invalid", "Invalid draft revision");
85181
- }
85182
- const locksDir = resolve7(mailDirReal, ".locks");
85183
- await ensureRealDirChain(mailDirReal, locksDir);
85184
- const lockPath = resolve7(locksDir, `${mail.id.trim()}.md.lock`);
85185
- try {
85186
- return await withFlock(lockPath, deleteLockTimeoutMs, async () => {
85187
- const revision = mail.revision.toLowerCase();
85188
- const content = await readFile4(target, "utf8").catch(() => null);
85189
- if (content === null) {
85190
- throw new MailAttachmentError("missing", "Draft no longer exists");
85191
- }
85192
- if (draftRevision(content) !== revision) {
85193
- throw new MailAttachmentError("changed", "Draft changed since it was loaded; refusing to edit");
85194
- }
85195
- if (!content.startsWith(`---
85196
- `)) {
85197
- throw new MailAttachmentError("invalid", "Draft has no frontmatter");
85198
- }
85199
- const end = content.indexOf(`
85200
- ---
85201
- `);
85202
- if (end < 0) {
85203
- throw new MailAttachmentError("invalid", "Draft has no frontmatter");
85204
- }
85205
- const parsed = parseMailMarkdown(content);
85206
- if (!parsed.attachments.includes(attachment)) {
85207
- throw new MailAttachmentError("missing", "Attachment is not linked to this draft");
85208
- }
85209
- const remaining = parsed.attachments.filter((path) => path !== attachment);
85210
- const frontmatterRaw = content.slice(4, end);
85211
- const newFrontmatter = rewriteFileFrontmatter(frontmatterRaw, remaining, new Date().toISOString());
85212
- if (newFrontmatter === null) {
85213
- throw new MailAttachmentError("invalid", "Draft frontmatter has no file list");
85214
- }
85215
- const newContent = `---
85216
- ${newFrontmatter}
85217
- ---
85218
- ${content.slice(end + `
85219
- ---
85220
- `.length)}`;
85221
- const tempPath = resolve7(mailDirReal, `${mail.id.trim()}.md.tmp`);
85222
- for (let attempt = 0;; attempt += 1) {
85223
- try {
85224
- await writeFile2(tempPath, newContent, { flag: "wx", mode: 384 });
85225
- break;
85226
- } catch (error51) {
85227
- if (error51.code !== "EEXIST")
85228
- throw error51;
85229
- const tempInfo = await lstat(tempPath).catch(() => {
85230
- return;
85231
- });
85232
- if (attempt === 0 && tempInfo?.isFile()) {
85233
- await unlink2(tempPath);
85234
- continue;
85235
- }
85236
- throw new MailAttachmentError("invalid", "Temp draft path is not a plain file; refusing to write through it");
85237
- }
85238
- }
85239
- const written = await readFile4(tempPath, "utf8").catch(() => null);
85240
- if (written !== newContent) {
85241
- throw new MailAttachmentError("changed", "Draft changed while writing; refusing to save");
85242
- }
85243
- const current = await readFile4(target, "utf8").catch(() => null);
85244
- if (current === null) {
85245
- throw new MailAttachmentError("missing", "Draft no longer exists");
85246
- }
85247
- if (draftRevision(current) !== revision) {
85248
- throw new MailAttachmentError("changed", "Draft changed since it was loaded; refusing to edit");
85249
- }
85250
- await rename2(tempPath, target);
85251
- return { revision: draftRevision(newContent) };
85252
- });
85253
- } catch (error51) {
85254
- if (error51 instanceof FlockTimeoutError) {
85255
- throw new MailAttachmentError("changed", "Draft is locked by another process (MailOS or another edit); try again");
85256
- }
85257
- throw error51;
85258
- }
85259
- }
85260
-
85261
- // plugins/mail/src/mailos.ts
85262
- import { execFile as execFileCb } from "node:child_process";
85263
- import { constants as constants3 } from "node:fs";
85264
- import { access, realpath as realpath4 } from "node:fs/promises";
85265
- import { homedir as homedir2 } from "node:os";
85266
- import { delimiter, isAbsolute as isAbsolute5, resolve as resolvePath, sep as sep5 } from "node:path";
85267
- import { promisify } from "node:util";
85268
- var defaultExecFileLike = promisify(execFileCb);
85269
-
85270
- class MailosCommandError extends Error {
85271
- code;
85272
- timedOut;
85273
- aborted;
85274
- exitCode;
85275
- constructor(code, message, details = {}) {
85276
- super(message);
85277
- this.name = "MailosCommandError";
85278
- this.code = code;
85279
- this.timedOut = details.timedOut ?? code === "timeout";
85280
- this.aborted = details.aborted ?? code === "aborted";
85281
- this.exitCode = details.exitCode ?? null;
85282
- }
85283
- }
85284
- function classifyExecError(error51) {
85285
- if (error51 instanceof MailosCommandError)
85286
- return error51;
85287
- if (!(error51 instanceof Error)) {
85288
- return new MailosCommandError("failed", "MailOS command failed");
85289
- }
85290
- if (error51.name === "AbortError") {
85291
- return new MailosCommandError("aborted", "MailOS command cancelled");
85292
- }
85293
- const execError = error51;
85294
- if (execError.killed) {
85295
- return new MailosCommandError("timeout", "MailOS command timed out", {
85296
- timedOut: true,
85297
- exitCode: typeof execError.code === "number" ? execError.code : null
85298
- });
85299
- }
85300
- return new MailosCommandError("failed", "MailOS command failed to complete", {
85301
- exitCode: typeof execError.code === "number" ? execError.code : null
85302
- });
85303
- }
85304
- async function runMailos(execFile, mailosBin, args, options) {
85305
- if (options.signal?.aborted) {
85306
- throw new MailosCommandError("aborted", "MailOS command cancelled");
85307
- }
85308
- try {
85309
- return await execFile(mailosBin, args, {
85310
- cwd: options.cwd,
85311
- timeout: options.timeoutMs,
85312
- maxBuffer: options.maxBufferBytes ?? 10 * 1024 * 1024,
85313
- windowsHide: true,
85314
- signal: options.signal
85315
- });
85316
- } catch (error51) {
85317
- const classified = classifyExecError(error51);
85318
- if (options.signal?.aborted && classified.code !== "aborted") {
85319
- throw new MailosCommandError("aborted", "MailOS command cancelled");
85320
- }
85321
- throw classified;
85322
- }
85323
- }
85324
-
85325
- class MailSendFailure extends Error {
85326
- reason;
85327
- constructor(reason, message) {
85328
- super(message);
85329
- this.name = "MailSendFailure";
85330
- this.reason = reason;
85331
- }
85332
- }
85333
- async function isExecutable(path) {
85334
- try {
85335
- await access(path, constants3.X_OK);
85336
- return true;
85337
- } catch {
85338
- return false;
85339
- }
85340
- }
85341
- function pathIsWithin(child, parent) {
85342
- const rel = child.startsWith(parent) ? child.slice(parent.length) : null;
85343
- if (!rel)
85344
- return false;
85345
- return rel === "" || rel.startsWith(sep5);
85346
- }
85347
- async function resolveMailosBinary({
85348
- cwd
85349
- }) {
85350
- const preferred = resolvePath(homedir2(), ".local", "bin", "mailos");
85351
- if (await isExecutable(preferred))
85352
- return preferred;
85353
- const pathEnv = process.env.PATH ?? "";
85354
- for (const dir of pathEnv.split(delimiter)) {
85355
- const trimmed = dir.trim();
85356
- if (!trimmed)
85357
- continue;
85358
- if (!isAbsolute5(trimmed))
85359
- continue;
85360
- const candidate = resolvePath(trimmed, "mailos");
85361
- if (!isAbsolute5(candidate))
85362
- continue;
85363
- if (pathIsWithin(candidate, cwd))
85364
- continue;
85365
- if (await isExecutable(candidate))
85366
- return candidate;
85367
- }
85368
- throw new Error("MailOS binary not found. Expected ~/.local/bin/mailos or an absolute PATH entry.");
85369
- }
85370
- var conversationCache = {
85371
- inFlight: new Map,
85372
- success: new Map
85373
- };
85374
- var conversationLimits = {
85375
- maxReferences: 12,
85376
- perReadTimeoutMs: 60000,
85377
- totalBudgetMs: 75000,
85378
- concurrency: 3,
85379
- successCacheSize: 40,
85380
- successTtlMs: 60000
85381
- };
85382
- function scopeKey(cwd, id) {
85383
- return `${cwd}\x00${id}`;
85384
- }
85385
- function parseConversationMarkdown(id, output) {
85386
- const subject = /^#\s+(.+)$/m.exec(output)?.[1]?.trim() ?? null;
85387
- const from = /^\*\*From:\*\*\s+(.+?)(?:\s{2,}|$)/m.exec(output)?.[1]?.trim() ?? null;
85388
- const to = /^\*\*To:\*\*\s+(.+?)(?:\s{2,}|$)/m.exec(output)?.[1]?.trim() ?? null;
85389
- const date5 = /^\*\*Date:\*\*\s+(.+?)(?:\s{2,}|$)/m.exec(output)?.[1]?.trim() ?? null;
85390
- const separator = output.indexOf(`
85391
- ---
85392
- `);
85393
- const body = (separator >= 0 ? output.slice(separator + 5) : output).trim();
85394
- return { body, date: date5, from, id, subject, to };
85395
- }
85396
- function requireDraftDir(draftDir) {
85397
- if (!draftDir || !isAbsolute5(draftDir)) {
85398
- throw new MailSendFailure("invalid-draft", "A send draft directory (the authorized mailDir) is required");
85399
- }
85400
- return draftDir;
85401
- }
85402
- function sameCanonicalDir(left, right) {
85403
- const canonical = async (value) => realpath4(value).catch(() => resolvePath(value));
85404
- return Promise.all([canonical(left), canonical(right)]).then(([leftReal, rightReal]) => leftReal === rightReal);
85405
- }
85406
- function mailosGlobalDraftDir() {
85407
- return resolvePath(homedir2(), ".email", "drafts");
85408
- }
85409
- function createMailosService(options = {}) {
85410
- const execFile = options.execFile ?? defaultExecFileLike;
85411
- const mailosBin = options.mailosBin;
85412
- const globalDraftDir = options.globalDraftDir;
85413
- async function binary(cwd) {
85414
- if (mailosBin) {
85415
- if (!isAbsolute5(mailosBin)) {
85416
- throw new Error("mailosBin must be an absolute path");
85417
- }
85418
- if (pathIsWithin(mailosBin, cwd)) {
85419
- throw new Error("mailosBin cannot live inside the working folder");
85420
- }
85421
- return mailosBin;
85422
- }
85423
- return resolveMailosBinary({ cwd });
85424
- }
85425
- function normalizeIdentityValue(value) {
85426
- return value && value !== "(not set)" ? value : null;
85427
- }
85428
- async function identity(cwd) {
85429
- try {
85430
- const bin = await binary(cwd);
85431
- const { stdout, stderr } = await runMailos(execFile, bin, ["whoami"], {
85432
- cwd,
85433
- timeoutMs: 15000
85434
- });
85435
- const output = [stdout, stderr].filter(Boolean).join(`
85436
- `);
85437
- const from = normalizeIdentityValue(/^Effective From:[ \t]*([^\r\n]+)$/im.exec(output)?.[1]?.trim() ?? null);
85438
- const name = normalizeIdentityValue(/^From Name:[ \t]*([^\r\n]+)$/im.exec(output)?.[1]?.trim() ?? null);
85439
- return { from, name, ok: Boolean(from ?? name) };
85440
- } catch {
85441
- return { from: null, name: null, ok: false };
85442
- }
85443
- }
85444
- async function signature(folder2, signal) {
85445
- try {
85446
- const bin = await binary(folder2);
85447
- const { stdout } = await runMailos(execFile, bin, ["sig", folder2], {
85448
- cwd: folder2,
85449
- timeoutMs: 15000,
85450
- signal
85451
- });
85452
- const output = stdout.trim();
85453
- if (!output)
85454
- return null;
85455
- if (output.includes("(no signature configured)"))
85456
- return null;
85457
- if (output.includes("(signature is empty)"))
85458
- return null;
85459
- return output;
85460
- } catch {
85461
- return null;
85462
- }
85463
- }
85464
- async function readMessage(cwd, id, signal) {
85465
- const bin = await binary(cwd);
85466
- const { stdout } = await runMailos(execFile, bin, ["read", id, "--markdown"], {
85467
- cwd,
85468
- timeoutMs: conversationLimits.perReadTimeoutMs,
85469
- signal
85470
- });
85471
- return parseConversationMarkdown(id, stdout);
85472
- }
85473
- function cachedRead(cwd, id, signal) {
85474
- const key = scopeKey(cwd, id);
85475
- const cached2 = conversationCache.success.get(key);
85476
- if (cached2) {
85477
- if (Date.now() - cached2.at < conversationLimits.successTtlMs) {
85478
- return Promise.resolve(cached2.message);
85479
- }
85480
- conversationCache.success.delete(key);
85481
- }
85482
- let entry = conversationCache.inFlight.get(key);
85483
- if (!entry) {
85484
- const controller = new AbortController;
85485
- const started = readMessage(cwd, id, controller.signal);
85486
- const created = {
85487
- promise: started,
85488
- controller,
85489
- signals: new Map
85490
- };
85491
- entry = created;
85492
- conversationCache.inFlight.set(key, created);
85493
- started.then((message) => {
85494
- const success2 = conversationCache.success;
85495
- if (success2.size >= conversationLimits.successCacheSize) {
85496
- success2.delete(success2.keys().next().value);
85497
- }
85498
- success2.set(key, { message, at: Date.now() });
85499
- }).catch(() => {}).finally(() => {
85500
- conversationCache.inFlight.delete(key);
85501
- for (const [subscriber, onAbort] of created.signals) {
85502
- subscriber.removeEventListener("abort", onAbort);
85503
- }
85504
- });
85505
- }
85506
- const active = entry;
85507
- if (signal) {
85508
- const onAbort = () => {
85509
- active.signals.delete(signal);
85510
- if (active.signals.size === 0)
85511
- active.controller.abort();
85512
- };
85513
- active.signals.set(signal, onAbort);
85514
- if (signal.aborted)
85515
- onAbort();
85516
- else
85517
- signal.addEventListener("abort", onAbort, { once: true });
85518
- }
85519
- if (!signal)
85520
- return active.promise;
85521
- return new Promise((resolveOuter, rejectOuter) => {
85522
- let settled = false;
85523
- const settleRace = (value) => {
85524
- if (settled)
85525
- return;
85526
- settled = true;
85527
- signal.removeEventListener("abort", onCallerAbort);
85528
- if (value instanceof Error)
85529
- rejectOuter(value);
85530
- else
85531
- resolveOuter(value);
85532
- };
85533
- const onCallerAbort = () => settleRace(new MailosCommandError("aborted", "MailOS command cancelled"));
85534
- active.promise.then((message) => settleRace(message), (error51) => settleRace(error51 instanceof Error ? error51 : new Error(String(error51))));
85535
- signal.addEventListener("abort", onCallerAbort, { once: true });
85536
- if (signal.aborted)
85537
- onCallerAbort();
85538
- });
85539
- }
85540
- async function conversation(cwd, references, signal) {
85541
- const ids = [
85542
- ...new Set(references.map((candidate) => candidate.trim()).filter((candidate) => Boolean(candidate) && !/\s/.test(candidate)))
85543
- ].slice(0, conversationLimits.maxReferences);
85544
- const items2 = [];
85545
- const missing = [];
85546
- if (!ids.length)
85547
- return { items: items2, missing, outcome: "empty" };
85548
- const deadline = Date.now() + conversationLimits.totalBudgetMs;
85549
- let timedOut = false;
85550
- const queued = ids.map((id) => ({ id, started: false, done: false }));
85551
- let nextIndex = 0;
85552
- const worker = async () => {
85553
- while (nextIndex < queued.length) {
85554
- const slot = queued[nextIndex];
85555
- if (!slot)
85556
- break;
85557
- nextIndex += 1;
85558
- slot.started = true;
85559
- try {
85560
- const message = await cachedRead(cwd, slot.id, signal);
85561
- if (!items2.some((item) => item.id === message.id)) {
85562
- items2.push(message);
85563
- }
85564
- } catch (error51) {
85565
- if (error51 instanceof MailosCommandError) {
85566
- if (error51.timedOut)
85567
- timedOut = true;
85568
- if (error51.aborted)
85569
- throw error51;
85570
- }
85571
- missing.push(slot.id);
85572
- }
85573
- if (Date.now() >= deadline) {
85574
- timedOut = true;
85575
- break;
85576
- }
85577
- }
85578
- };
85579
- const workers = Array.from({ length: Math.min(conversationLimits.concurrency, ids.length) }, () => worker());
85580
- await Promise.all(workers);
85581
- if (timedOut)
85582
- return { items: items2, missing, outcome: "timeout" };
85583
- if (!items2.length && missing.length === ids.length) {
85584
- return { items: items2, missing, outcome: "not-found" };
85585
- }
85586
- if (missing.length > 0)
85587
- return { items: items2, missing, outcome: "partial" };
85588
- return { items: items2, missing, outcome: "ok" };
85589
- }
85590
- async function sendPreview(cwd, id, options2 = {}) {
85591
- const draftDir = requireDraftDir(options2.draftDir);
85592
- const bin = await binary(cwd);
85593
- const args = ["send", "draft", id, "--draft-dir", draftDir, "--preview"];
85594
- const { stdout, stderr } = await runMailos(execFile, bin, args, {
85595
- cwd,
85596
- timeoutMs: 30000,
85597
- signal: options2.signal
85598
- });
85599
- const output = [stdout, stderr].filter(Boolean).join(`
85600
- `);
85601
- const revision = /^Revision:\s*([a-f0-9]{64})\s*$/im.exec(output)?.[1];
85602
- if (!revision) {
85603
- throw new MailSendFailure("invalid-draft", "MailOS did not return a revision; the draft is not sendable.");
85604
- }
85605
- return { dryRun: true, output, revision };
85606
- }
85607
- async function confirmSend(cwd, id, revision, options2 = {}) {
85608
- if (!/^[a-f0-9]{64}$/i.test(revision)) {
85609
- throw new MailSendFailure("revision-match", "Invalid draft revision");
85610
- }
85611
- const draftDir = requireDraftDir(options2.draftDir);
85612
- const bin = await binary(cwd);
85613
- const args = [
85614
- "send",
85615
- "draft",
85616
- id,
85617
- "--draft-dir",
85618
- draftDir,
85619
- "--draft-revision",
85620
- revision,
85621
- "--delete-after=false"
85622
- ];
85623
- const { stdout, stderr } = await runMailos(execFile, bin, args, {
85624
- cwd,
85625
- timeoutMs: 60000,
85626
- signal: options2.signal
85627
- });
85628
- const output = [stdout, stderr].filter(Boolean).join(`
85629
- `);
85630
- if (/\n?\s*❌\s*Draft\s+\S+\s+changed after preview/.test(output)) {
85631
- throw new MailSendFailure("revision-match", "Draft changed after preview; refresh and preview again before sending");
85632
- }
85633
- const contract = validateSendContract(output);
85634
- if (!contract.ok) {
85635
- throw new MailSendFailure("contract", contract.reason);
85636
- }
85637
- return { dryRun: false, output };
85638
- }
85639
- async function archive(cwd, id, options2 = {}) {
85640
- const draftDir = requireDraftDir(options2.draftDir);
85641
- if (!await sameCanonicalDir(draftDir, globalDraftDir ?? mailosGlobalDraftDir())) {
85642
- throw new MailSendFailure("unsupported-draft-dir", "MailOS drafts archive can only address the default ~/.email/drafts folder; this draft lives elsewhere. Delete it or archive it directly with mailos.");
85643
- }
85644
- const bin = await binary(cwd);
85645
- await runMailos(execFile, bin, ["drafts", "archive", id], {
85646
- cwd,
85647
- timeoutMs: 30000,
85648
- signal: options2.signal
85649
- });
85650
- }
85651
- return {
85652
- identity,
85653
- signature,
85654
- conversation,
85655
- sendPreview,
85656
- confirmSend,
85657
- archive
85658
- };
85659
- }
85660
- function validateSendContract(output) {
85661
- const markers = /^\s*(❌|⏭️)/m.exec(output);
85662
- if (markers) {
85663
- return { ok: false, reason: "MailOS reported a failed or skipped draft" };
85664
- }
85665
- if (output.includes("No draft files found to send")) {
85666
- return { ok: false, reason: "MailOS did not find the draft to send" };
85667
- }
85668
- const errors3 = /^\s*❌\s*Errors:\s*(\d+)/m.exec(output);
85669
- if (errors3?.[1] && Number(errors3[1]) > 0) {
85670
- return {
85671
- ok: false,
85672
- reason: `MailOS reported ${errors3[1]} send error(s)`
85673
- };
85674
- }
85675
- const sent = /^\s*✅\s*(?:Sent|Previewed|Would send):\s*(\d+)/m.exec(output);
85676
- if (!sent || Number(sent[1]) < 1) {
85677
- return { ok: false, reason: "MailOS did not confirm the draft was sent" };
85678
- }
85679
- return { ok: true };
85680
- }
85681
-
85682
- // plugins/mail/src/node.ts
85683
- import { createHash as createHash5 } from "node:crypto";
85684
- import { realpathSync as realpathSync2 } from "node:fs";
85685
- import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
85686
- import { homedir as homedir3 } from "node:os";
85687
- import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve8, sep as sep6 } from "node:path";
85688
- function defaultMailDir() {
85689
- return resolve8(homedir3(), ".email/drafts");
85690
- }
85691
- function draftFilePath(mailDir, id) {
85692
- const trimmed = id.trim();
85693
- if (!trimmed || trimmed === "." || trimmed === ".." || /[\\/]/.test(trimmed)) {
85694
- throw new Error("Invalid mail id");
85695
- }
85696
- return resolve8(mailDir, `${trimmed}.md`);
85697
- }
85698
- var activeStatuses = new Set(["working", "ready"]);
85699
- function isActiveStatus(status) {
85700
- return status !== null && activeStatuses.has(status);
85701
- }
85702
- function canonicalDraftPath(path) {
85703
- try {
85704
- return realpathSync2(resolve8(path));
85705
- } catch {
85706
- return null;
85707
- }
85708
- }
85709
- function isFolderWithin(root, candidate) {
85710
- if (!candidate)
85711
- return false;
85712
- const anchor = canonicalDraftPath(root);
85713
- if (anchor === null)
85714
- return false;
85715
- const target = canonicalDraftPath(candidate);
85716
- if (target === null)
85717
- return false;
85718
- if (anchor === target)
85719
- return true;
85720
- const rel = relative7(anchor, target);
85721
- if (!rel)
85722
- return true;
85723
- if (rel === ".." || rel.startsWith(`..${sep6}`))
85724
- return false;
85725
- return !isAbsolute6(rel);
85726
- }
85727
- function sortDateKey(value) {
85728
- const parsed = value ? Date.parse(value) : Number.NaN;
85729
- if (Number.isNaN(parsed))
85730
- return 0;
85731
- return parsed;
85732
- }
85733
- function sortMail(items2) {
85734
- items2.sort((a, b) => {
85735
- const aKey = sortDateKey(a.updatedAt ?? a.createdAt);
85736
- const bKey = sortDateKey(b.updatedAt ?? b.createdAt);
85737
- return bKey - aKey;
85738
- });
85739
- return items2;
85740
- }
85741
- function draftRevision2(content) {
85742
- return createHash5("sha256").update(content).digest("hex");
85743
- }
85744
- async function mailFromFile(filePath) {
85745
- const content = await readFile5(filePath, "utf8").catch(() => null);
85746
- if (!content)
85747
- return null;
85748
- const parsed = parseMailMarkdown(content);
85749
- if (!parsed.id)
85750
- return null;
85751
- if (!isActiveStatus(parsed.status))
85752
- return null;
85753
- return {
85754
- id: parsed.id,
85755
- status: parsed.status,
85756
- tags: parsed.tags,
85757
- folder: parsed.folder,
85758
- attachments: parsed.attachments,
85759
- from: parsed.from,
85760
- inReplyTo: parsed.inReplyTo,
85761
- references: parsed.references,
85762
- to: parsed.to,
85763
- cc: parsed.cc,
85764
- bcc: parsed.bcc,
85765
- subject: parsed.subject,
85766
- createdAt: parsed.createdAt,
85767
- updatedAt: parsed.updatedAt,
85768
- noSignature: parsed.noSignature,
85769
- body: parsed.body,
85770
- filePath,
85771
- revision: draftRevision2(content)
85772
- };
85773
- }
85774
- async function listMailForFolder({
85775
- mailDir = defaultMailDir(),
85776
- folder: folder2
85777
- }) {
85778
- const entries = await readdir3(mailDir, { withFileTypes: true });
85779
- const items2 = [];
85780
- for (const entry of entries) {
85781
- if (!entry.isFile())
85782
- continue;
85783
- if (!entry.name.endsWith(".md"))
85784
- continue;
85785
- const filePath = resolve8(mailDir, entry.name);
85786
- const mail = await mailFromFile(filePath);
85787
- if (!mail)
85788
- continue;
85789
- if (!isFolderWithin(folder2, mail.folder))
85790
- continue;
85791
- items2.push({
85792
- id: mail.id,
85793
- status: mail.status,
85794
- tags: mail.tags,
85795
- folder: mail.folder,
85796
- attachments: mail.attachments,
85797
- from: mail.from,
85798
- inReplyTo: mail.inReplyTo,
85799
- references: mail.references,
85800
- to: mail.to,
85801
- cc: mail.cc,
85802
- bcc: mail.bcc,
85803
- subject: mail.subject,
85804
- createdAt: mail.createdAt,
85805
- updatedAt: mail.updatedAt,
85806
- noSignature: mail.noSignature,
85807
- excerpt: mailExcerpt(mail.body),
85808
- filePath,
85809
- revision: mail.revision
85810
- });
85811
- }
85812
- return sortMail(items2);
85813
- }
85814
- async function readMailById(id, mailDir = defaultMailDir()) {
85815
- return mailFromFile(draftFilePath(mailDir, id));
85816
- }
85817
-
85818
- // plugins/mail/src/api.ts
85819
- var defaultMailos = createMailosService();
85820
- function defaultPreviewer(mailos) {
85821
- return async ({ mail }) => ({
85822
- body: mail.body,
85823
- signature: mail.noSignature ? null : mail.folder ? await mailos.signature(mail.folder) : null
85824
- });
85825
- }
85826
- function mailDirOption(mailDir) {
85827
- return mailDir ?? defaultMailDir();
85828
- }
85829
- function requireString(value, name) {
85830
- if (typeof value !== "string" || !value.trim()) {
85831
- throw apiError("bad-request", `Missing ${name} parameter`);
85832
- }
85833
- return value.trim();
85834
- }
85835
- function requireRevision(value) {
85836
- const revision = requireString(value, "revision");
85837
- if (!/^[a-f0-9]{64}$/i.test(revision)) {
85838
- throw apiError("bad-request", "Invalid revision parameter");
85839
- }
85840
- return revision.toLowerCase();
85841
- }
85842
- async function authorizedMail(context, id, pathValue) {
85843
- const trimmed = id.trim();
85844
- if (!trimmed)
85845
- throw apiError("bad-request", "Missing id parameter");
85846
- const folder2 = context.authorizeFolder(pathValue);
85847
- const mail = await readMailById(trimmed, mailDirOption(context.mailDir));
85848
- if (!mail)
85849
- throw apiError("not-found", "Mail not found");
85850
- if (!mail.folder)
85851
- throw apiError("not-found", "Mail has no folder");
85852
- if (!isFolderWithin(folder2, mail.folder)) {
85853
- throw apiError("not-found", "Mail not found for this folder");
85854
- }
85855
- return { folder: folder2, mail: { ...mail, folder: mail.folder } };
85856
- }
85857
- var listMail = async ({ context, query, response }) => {
85858
- const folder2 = context.authorizeFolder(query.get("path"));
85859
- const items2 = await listMailForFolder({
85860
- folder: folder2,
85861
- mailDir: mailDirOption(context.mailDir)
85862
- });
85863
- jsonResponse(response, 200, { folder: folder2, items: items2 });
85864
- };
85865
- var readItem = async ({ context, query, response }) => {
85866
- const id = requireString(query.get("id"), "id");
85867
- const { folder: folder2, mail } = await authorizedMail(context, id, query.get("path"));
85868
- context.watch(folder2);
85869
- let preview;
85870
- try {
85871
- const previewer = context.mailPreviewer ?? defaultPreviewer(context.mailos ?? defaultMailos);
85872
- preview = await previewer({ id, folder: folder2, mail });
85873
- } catch (error51) {
85874
- const detail = error51 instanceof Error ? error51.message : "Unknown preview error";
85875
- throw apiError("internal", `Mail preview failed: ${detail}`);
85876
- }
85877
- jsonResponse(response, 200, { mail, preview });
85878
- };
85879
- var resolveSender = async ({ context, query, response }) => {
85880
- const id = requireString(query.get("id"), "id");
85881
- const { mail } = await authorizedMail(context, id, query.get("path"));
85882
- const mailos = context.mailos ?? defaultMailos;
85883
- const identity = await mailos.identity(mail.folder);
85884
- jsonResponse(response, 200, identity);
85885
- };
85886
- var readConversation = async ({
85887
- context,
85888
- query,
85889
- response,
85890
- signal
85891
- }) => {
85892
- const id = requireString(query.get("id"), "id");
85893
- const { mail } = await authorizedMail(context, id, query.get("path"));
85894
- const mailos = context.mailos ?? defaultMailos;
85895
- const references = [...mail.references, mail.inReplyTo].filter((reference) => Boolean(reference));
85896
- const result = await mailos.conversation(mail.folder, references, signal);
85897
- jsonResponse(response, 200, result);
85898
- };
85899
- function commandFailureError(error51) {
85900
- if (error51.timedOut || error51.code === "timeout") {
85901
- throw apiError("timeout", "MailOS command timed out");
85902
- }
85903
- if (error51.code === "aborted")
85904
- throw error51;
85905
- throw apiError("failed", "MailOS command failed");
85906
- }
85907
- function sendFailureError(error51) {
85908
- switch (error51.reason) {
85909
- case "revision-match":
85910
- return apiError("conflict", error51.message);
85911
- case "invalid-draft":
85912
- return apiError("bad-request", error51.message);
85913
- case "unsupported-draft-dir":
85914
- return apiError("bad-request", error51.message);
85915
- default:
85916
- return apiError("failed", error51.message);
85917
- }
85918
- }
85919
- var runAction = async ({ body, context, response, signal }) => {
85920
- const parsed = parseJsonBody(body);
85921
- const action = requireString(parsed.action, "action");
85922
- const id = requireString(parsed.id, "id");
85923
- const revision = requireRevision(parsed.revision);
85924
- if (action !== "archive" && action !== "delete" && action !== "remove-attachment") {
85925
- throw apiError("bad-request", "Invalid action parameter");
85926
- }
85927
- const { mail } = await authorizedMail(context, id, pathFromParsed(parsed));
85928
- if (mail.revision !== revision) {
85929
- throw apiError("conflict", "Draft changed since it was loaded");
85930
- }
85931
- const mailos = context.mailos ?? defaultMailos;
85932
- if (action === "remove-attachment") {
85933
- const attachment = requireString(parsed.attachment, "attachment");
85934
- try {
85935
- await removeDraftAttachmentVerified(mail, mailDirOption(context.mailDir), attachment);
85936
- } catch (error51) {
85937
- if (error51 instanceof MailAttachmentError) {
85938
- switch (error51.outcome) {
85939
- case "missing":
85940
- throw apiError("not-found", error51.message);
85941
- case "invalid":
85942
- throw apiError("bad-request", error51.message);
85943
- case "changed":
85944
- throw apiError("conflict", error51.message);
85945
- }
85946
- }
85947
- throw error51;
85948
- }
85949
- } else if (action === "delete") {
85950
- try {
85951
- await deleteLocalDraftVerified(mail, mailDirOption(context.mailDir));
85952
- } catch (error51) {
85953
- if (error51 instanceof MailDeleteError) {
85954
- switch (error51.outcome) {
85955
- case "missing":
85956
- throw apiError("not-found", error51.message);
85957
- case "invalid":
85958
- throw apiError("bad-request", error51.message);
85959
- case "changed":
85960
- throw apiError("conflict", error51.message);
85961
- }
85962
- }
85963
- throw error51;
85964
- }
85965
- } else {
85966
- try {
85967
- await mailos.archive(mail.folder, mail.id, {
85968
- draftDir: mailDirOption(context.mailDir),
85969
- signal
85970
- });
85971
- } catch (error51) {
85972
- if (error51 instanceof MailSendFailure)
85973
- throw sendFailureError(error51);
85974
- if (error51 instanceof MailosCommandError)
85975
- commandFailureError(error51);
85976
- throw error51;
85977
- }
85978
- }
85979
- jsonResponse(response, 200, { action, id: mail.id, ok: true });
85980
- };
85981
- var runSend = async ({ body, context, response, signal }) => {
85982
- const parsed = parseJsonBody(body);
85983
- const id = requireString(parsed.id, "id");
85984
- const dryRun = parsed.dryRun === true;
85985
- const { mail } = await authorizedMail(context, id, pathFromParsed(parsed));
85986
- const mailos = context.mailos ?? defaultMailos;
85987
- if (dryRun) {
85988
- const preview = await mailos.sendPreview(mail.folder, mail.id, {
85989
- draftDir: mailDirOption(context.mailDir),
85990
- signal
85991
- }).catch((error51) => {
85992
- if (error51 instanceof MailSendFailure)
85993
- throw sendFailureError(error51);
85994
- if (error51 instanceof MailosCommandError)
85995
- commandFailureError(error51);
85996
- throw error51;
85997
- });
85998
- jsonResponse(response, 200, preview);
85999
- return;
86000
- }
86001
- const revision = requireRevision(parsed.revision);
86002
- if (mail.revision !== revision) {
86003
- throw apiError("conflict", "Draft changed since it was loaded");
86004
- }
86005
- const result = await mailos.confirmSend(mail.folder, mail.id, revision, {
86006
- draftDir: mailDirOption(context.mailDir),
86007
- signal
86008
- }).catch((error51) => {
86009
- if (error51 instanceof MailSendFailure)
86010
- throw sendFailureError(error51);
86011
- if (error51 instanceof MailosCommandError)
86012
- commandFailureError(error51);
86013
- throw error51;
86014
- });
86015
- jsonResponse(response, 200, result);
86016
- };
86017
- function pathFromParsed(parsed) {
86018
- return typeof parsed.path === "string" && parsed.path ? parsed.path : null;
86019
- }
86020
- async function handleMailApiRoutes(request, response, context) {
86021
- const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
86022
- const specByPath = {
86023
- "/api/mail": { method: "GET", handle: listMail },
86024
- "/api/mail/item": { method: "GET", handle: readItem },
86025
- "/api/mail/from": { method: "GET", handle: resolveSender },
86026
- "/api/mail/conversation": {
86027
- method: "GET",
86028
- timeoutMs: 75000,
86029
- handle: readConversation
86030
- },
86031
- "/api/mail/action": {
86032
- method: "POST",
86033
- maxBodyBytes: 16 * 1024,
86034
- timeoutMs: 60000,
86035
- handle: runAction
86036
- },
86037
- "/api/mail/send": {
86038
- method: "POST",
86039
- maxBodyBytes: 16 * 1024,
86040
- timeoutMs: 90000,
86041
- handle: runSend
86042
- }
86043
- };
86044
- const spec = specByPath[url2.pathname];
86045
- if (!spec)
86046
- return false;
86047
- await runApiRoute(request, response, url2, spec, context);
86048
- return true;
86049
- }
86050
-
86051
- // plugins/tasks/src/api.ts
86052
- import { readdir as readdir4, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
86053
- import { dirname as dirname7, resolve as resolve9 } from "node:path";
86054
-
86055
- // packages/folder-plugin/src/validation.ts
86056
- function parseJsonSource(source, validate) {
86057
- let value;
86058
- try {
86059
- value = JSON.parse(source);
86060
- } catch (error51) {
86061
- const detail = error51 instanceof Error && error51.message ? error51.message : "could not be parsed";
86062
- return {
86063
- ok: false,
86064
- issues: [{ path: "#", message: `not valid JSON: ${detail}` }]
86065
- };
86066
- }
86067
- const issues = validate(value);
86068
- if (issues.length > 0)
86069
- return { ok: false, issues };
86070
- return { ok: true, document: value };
86071
- }
86072
- function issueSummary(issues, file3 = "content file") {
86073
- const first = issues[0];
86074
- if (!first)
86075
- return `${file3} is valid`;
86076
- const label = first.path && first.path !== "#" ? first.path : file3;
86077
- if (issues.length === 1)
86078
- return `${label}: ${first.message}`;
86079
- return `${issues.length} problems found in ${file3} (first: ${label} — ${first.message})`;
86080
- }
86081
-
86082
- // plugins/tasks/src/data/parse.ts
86083
- var TASK_TYPES = [
86084
- "task",
86085
- "bug",
86086
- "feature",
86087
- "chore",
86088
- "spike",
86089
- "epic",
86090
- "milestone"
86091
- ];
86092
- var TASK_STATUSES = [
86093
- "todo",
86094
- "in_progress",
86095
- "blocked",
86096
- "review",
86097
- "done",
86098
- "canceled"
86099
- ];
86100
- var LINK_PROVIDERS = ["github", "linear", "jira", "web"];
86101
- var LINK_KINDS = [
86102
- "issue",
86103
- "pull_request",
86104
- "commit",
86105
- "repository",
86106
- "project",
86107
- "document",
86108
- "design",
86109
- "discussion",
86110
- "external"
86111
- ];
86112
- var LINK_RELATIONS = [
86113
- "references",
86114
- "implemented_by",
86115
- "fixes",
86116
- "reported_by",
86117
- "spec",
86118
- "design",
86119
- "review",
86120
- "deployment",
86121
- "blocked_by"
86122
- ];
86123
- var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
86124
- function isRecord(value) {
86125
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
86126
- }
86127
- function nonEmptyString(value) {
86128
- return typeof value === "string" && value.trim().length > 0;
86129
- }
86130
- function todoIssueSummary(issues) {
86131
- return issueSummary(issues, "todo.json");
86132
- }
86133
- function validateTodoFile(value) {
86134
- if (!isRecord(value)) {
86135
- return [{ path: "#", message: "todo.json must contain a JSON object" }];
86136
- }
86137
- const issues = [];
86138
- if (value.version !== undefined && value.version !== 1 && value.version !== 2) {
86139
- issues.push({
86140
- path: "version",
86141
- message: `must be 1 or 2; got ${JSON.stringify(value.version)}`
86142
- });
86143
- }
86144
- if (value.$schema !== undefined && !nonEmptyString(value.$schema)) {
86145
- issues.push({ path: "$schema", message: "must be a non-empty string" });
86146
- }
86147
- if (value.id !== undefined && !nonEmptyString(value.id)) {
86148
- issues.push({ path: "id", message: "must be a non-empty string" });
86149
- }
86150
- if (value.title !== undefined && !nonEmptyString(value.title)) {
86151
- issues.push({ path: "title", message: "must be a non-empty string" });
86152
- }
86153
- if (!Array.isArray(value.tasks)) {
86154
- issues.push({ path: "tasks", message: "must be an array of tasks" });
86155
- return issues;
86156
- }
86157
- const ids = new Set;
86158
- const keys = new Set;
86159
- value.tasks.forEach((task, index) => {
86160
- validateTask(task, `tasks[${index}]`, issues, ids, keys);
86161
- });
86162
- value.tasks.forEach((task, index) => {
86163
- if (!isRecord(task) || !Array.isArray(task.relations))
86164
- return;
86165
- task.relations.forEach((relation, relIndex) => {
86166
- if (!isRecord(relation))
86167
- return;
86168
- const target = relation.targetTaskId;
86169
- if (typeof target === "string" && !ids.has(target)) {
86170
- issues.push({
86171
- path: `tasks[${index}].relations[${relIndex}].targetTaskId`,
86172
- message: `references unknown task id ${JSON.stringify(target)}`
86173
- });
86174
- }
86175
- });
86176
- });
86177
- return issues;
86178
- }
86179
- function validateTask(task, path, issues, ids, keys) {
86180
- if (!isRecord(task)) {
86181
- issues.push({ path, message: "must be a task object" });
86182
- return;
86183
- }
86184
- if (!nonEmptyString(task.name)) {
86185
- issues.push({
86186
- path: `${path}.name`,
86187
- message: "required; a task needs a non-empty name"
86188
- });
86189
- }
86190
- if (task.id !== undefined) {
86191
- if (!nonEmptyString(task.id)) {
86192
- issues.push({ path: `${path}.id`, message: "must be a non-empty string" });
86193
- } else if (ids.has(task.id)) {
86194
- issues.push({
86195
- path: `${path}.id`,
86196
- message: `duplicate task id ${JSON.stringify(task.id)}`
86197
- });
86198
- } else {
86199
- ids.add(task.id);
86200
- }
86201
- }
86202
- if (task.key !== undefined) {
86203
- if (!nonEmptyString(task.key)) {
86204
- issues.push({
86205
- path: `${path}.key`,
86206
- message: "must be a non-empty string"
86207
- });
86208
- } else if (keys.has(task.key)) {
86209
- issues.push({
86210
- path: `${path}.key`,
86211
- message: `duplicate task key ${JSON.stringify(task.key)}`
86212
- });
86213
- } else {
86214
- keys.add(task.key);
86215
- }
86216
- }
86217
- if (task.type !== undefined && !inEnum(task.type, TASK_TYPES)) {
86218
- issues.push({
86219
- path: `${path}.type`,
86220
- message: `must be one of ${TASK_TYPES.join(", ")}; got ${JSON.stringify(task.type)}`
86221
- });
86222
- }
86223
- if (task.status !== undefined && !inEnum(task.status, TASK_STATUSES)) {
86224
- issues.push({
86225
- path: `${path}.status`,
86226
- message: `must be one of ${TASK_STATUSES.join(", ")}; got ${JSON.stringify(task.status)}`
86227
- });
86228
- }
86229
- if (task.done !== undefined && typeof task.done !== "boolean") {
86230
- issues.push({ path: `${path}.done`, message: "must be a boolean" });
86231
- }
86232
- if (task.priority !== undefined && task.priority !== null) {
86233
- const priority = task.priority;
86234
- if (typeof priority !== "number" || !Number.isInteger(priority) || priority < 1 || priority > 5) {
86235
- issues.push({
86236
- path: `${path}.priority`,
86237
- message: "must be an integer between 1 and 5"
86238
- });
86239
- }
86240
- }
86241
- if (task.deadline !== undefined && task.deadline !== null && (typeof task.deadline !== "string" || !DATE_PATTERN.test(task.deadline))) {
86242
- issues.push({
86243
- path: `${path}.deadline`,
86244
- message: "must be an ISO date (YYYY-MM-DD)"
86245
- });
86246
- }
86247
- for (const section of ["acceptanceCriteria", "steps"]) {
86248
- const items2 = task[section];
86249
- if (items2 === undefined)
86250
- continue;
86251
- if (!Array.isArray(items2)) {
86252
- issues.push({
86253
- path: `${path}.${section}`,
86254
- message: "must be an array of check items"
86255
- });
86256
- continue;
86257
- }
86258
- items2.forEach((item, index) => {
86259
- validateCheckItem(item, `${path}.${section}[${index}]`, issues);
86260
- });
86261
- }
86262
- if (task.links !== undefined) {
86263
- if (!Array.isArray(task.links)) {
86264
- issues.push({
86265
- path: `${path}.links`,
86266
- message: "must be an array of links"
86267
- });
86268
- } else {
86269
- task.links.forEach((link, index) => {
86270
- validateLink(link, `${path}.links[${index}]`, issues);
86271
- });
86272
- }
86273
- }
86274
- if (task.notes !== undefined) {
86275
- if (!Array.isArray(task.notes)) {
86276
- issues.push({
86277
- path: `${path}.notes`,
86278
- message: "must be an array of note references"
86279
- });
86280
- } else {
86281
- task.notes.forEach((note, index) => {
86282
- validateNoteRef(note, `${path}.notes[${index}]`, issues);
86283
- });
86284
- }
86285
- }
86286
- }
86287
- var UNSAFE_NOTE_REFERENCE = /^(?:\/|\\\\|[a-z][a-z0-9+.-]*:|~)/i;
86288
- function validateNoteRef(note, path, issues) {
86289
- if (!nonEmptyString(note)) {
86290
- issues.push({ path, message: "must be a non-empty string" });
86291
- return;
86292
- }
86293
- if (/^https?:\/\//i.test(note)) {
86294
- let url2;
86295
- try {
86296
- url2 = new URL(note);
86297
- } catch {
86298
- issues.push({ path, message: "is not a valid absolute URL" });
86299
- return;
86300
- }
86301
- if (url2.username || url2.password) {
86302
- issues.push({ path, message: "must not contain embedded credentials" });
86303
- return;
86304
- }
86305
- const isLocalhost = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "::1";
86306
- if (url2.protocol !== "https:" && !(url2.protocol === "http:" && isLocalhost)) {
86307
- issues.push({
86308
- path,
86309
- message: "must use https, except localhost development links"
86310
- });
86311
- }
86312
- return;
86313
- }
86314
- if (UNSAFE_NOTE_REFERENCE.test(note) || note.split(/[\\/]/).includes("..")) {
86315
- issues.push({
86316
- path,
86317
- message: "must be a folder-relative path (or an https URL)"
86318
- });
86319
- }
86320
- }
86321
- function validateCheckItem(item, path, issues) {
86322
- if (!isRecord(item)) {
86323
- issues.push({
86324
- path,
86325
- message: "must be an object with id, text and done"
86326
- });
86327
- return;
86328
- }
86329
- if (!nonEmptyString(item.id)) {
86330
- issues.push({ path: `${path}.id`, message: "required non-empty string" });
86331
- }
86332
- if (!nonEmptyString(item.text)) {
86333
- issues.push({ path: `${path}.text`, message: "required non-empty string" });
86334
- }
86335
- if (typeof item.done !== "boolean") {
86336
- issues.push({ path: `${path}.done`, message: "required boolean" });
86337
- }
86338
- }
86339
- function validateLink(link, path, issues) {
86340
- if (!isRecord(link)) {
86341
- issues.push({ path, message: "must be a link object" });
86342
- return;
86343
- }
86344
- for (const field of ["id", "provider", "kind", "relation", "url"]) {
86345
- if (!nonEmptyString(link[field])) {
86346
- issues.push({
86347
- path: `${path}.${field}`,
86348
- message: "required non-empty string"
86349
- });
86350
- }
86351
- }
86352
- if (link.provider !== undefined && !inEnum(link.provider, LINK_PROVIDERS)) {
86353
- issues.push({
86354
- path: `${path}.provider`,
86355
- message: `must be one of ${LINK_PROVIDERS.join(", ")}; got ${JSON.stringify(link.provider)}`
86356
- });
86357
- }
86358
- if (link.kind !== undefined && !inEnum(link.kind, LINK_KINDS)) {
86359
- issues.push({
86360
- path: `${path}.kind`,
86361
- message: `must be one of ${LINK_KINDS.join(", ")}; got ${JSON.stringify(link.kind)}`
86362
- });
86363
- }
86364
- if (link.relation !== undefined && !inEnum(link.relation, LINK_RELATIONS)) {
86365
- issues.push({
86366
- path: `${path}.relation`,
86367
- message: `must be one of ${LINK_RELATIONS.join(", ")}; got ${JSON.stringify(link.relation)}`
86368
- });
86369
- }
86370
- const urlValue = link.url;
86371
- if (typeof urlValue === "string") {
86372
- let url2;
86373
- try {
86374
- url2 = new URL(urlValue);
86375
- } catch {
86376
- issues.push({
86377
- path: `${path}.url`,
86378
- message: "is not a valid absolute URL"
86379
- });
86380
- return;
86381
- }
86382
- if (url2.username || url2.password) {
86383
- issues.push({
86384
- path: `${path}.url`,
86385
- message: "must not contain embedded credentials"
86386
- });
86387
- }
86388
- const isLocalhost = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "::1";
86389
- if (url2.protocol !== "https:" && !(url2.protocol === "http:" && isLocalhost)) {
86390
- issues.push({
86391
- path: `${path}.url`,
86392
- message: "must use https, except localhost development links"
86393
- });
86394
- }
86395
- if (link.provider === "github" && url2.hostname !== "github.com") {
86396
- issues.push({
86397
- path: `${path}.url`,
86398
- message: "github links must use github.com"
86399
- });
86400
- }
86401
- if (link.provider === "linear" && url2.hostname !== "linear.app" && !url2.hostname.endsWith(".linear.app")) {
86402
- issues.push({
86403
- path: `${path}.url`,
86404
- message: "linear links must use linear.app"
86405
- });
86406
- }
86407
- if (link.provider === "jira" && !url2.hostname.endsWith(".atlassian.net") && url2.hostname !== "jira.atlassian.com") {
86408
- issues.push({
86409
- path: `${path}.url`,
86410
- message: "jira links must use an Atlassian Jira host"
86411
- });
86412
- }
86413
- }
86414
- }
86415
- function inEnum(value, allowed) {
86416
- return allowed.includes(String(value));
86417
- }
86418
- function todoFileWarnings(value) {
86419
- if (!isRecord(value) || !Array.isArray(value.tasks))
86420
- return [];
86421
- return value.tasks.flatMap((task, index) => isRecord(task) && Array.isArray(task.notes) && task.notes.some((note) => nonEmptyString(note)) ? [] : [
86422
- {
86423
- message: "no notes; link the accompanying note file or https reference",
86424
- path: `tasks[${index}].notes`,
86425
- severity: "warning"
86426
- }
86427
- ]);
86428
- }
86429
- function parseTodoSource(source) {
86430
- return parseJsonSource(source, validateTodoFile);
86431
- }
86432
- function starterTodoSource(folder2) {
86433
- const title = folder2.trim().split(/[\\/]/).filter(Boolean).at(-1) ?? "Tasks";
86434
- return `${JSON.stringify({
86435
- $schema: "https://raggle.co/schemas/tasks.json",
86436
- version: 2,
86437
- id: "todo",
86438
- title,
86439
- people: [],
86440
- projects: [],
86441
- cycles: [],
86442
- milestones: [],
86443
- components: [],
86444
- areas: [],
86445
- integrations: [],
86446
- customFields: [],
86447
- automations: [],
86448
- tasks: [
86449
- {
86450
- id: "todo:0",
86451
- key: "TODO-1",
86452
- index: 0,
86453
- rank: "0001",
86454
- name: "First task",
86455
- description: "Describe what needs to happen.",
86456
- type: "task",
86457
- status: "todo",
86458
- done: false,
86459
- priority: 3,
86460
- deadline: null,
86461
- assigneeIds: [],
86462
- acceptanceCriteria: [
86463
- { id: "ac-1", text: "Acceptance check", done: false }
86464
- ]
86465
- }
86466
- ]
86467
- }, null, 2)}
86468
- `;
86469
- }
86470
-
86471
- // packages/folder-plugin/src/client.ts
86472
- function folderPathId(path) {
86473
- const bytes = new TextEncoder().encode(path);
86474
- let binary = "";
86475
- for (const byte of bytes)
86476
- binary += String.fromCharCode(byte);
86477
- return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
86478
- }
86479
-
86480
- // plugins/tasks/src/data/task-id.ts
86481
- var QUALIFIER = "@";
86482
- var REF_PATTERN = /^[A-Za-z0-9_-]+$/;
86483
- function parseQualifiedTaskId(value) {
86484
- const at = value.indexOf(QUALIFIER);
86485
- if (at < 1)
86486
- return;
86487
- const ref = value.slice(0, at);
86488
- const id = value.slice(at + 1);
86489
- if (!id || !REF_PATTERN.test(ref))
86490
- return;
86491
- return { id, ref };
86492
- }
86493
- function refMatchesFolder(ref, sourceFolder) {
86494
- return typeof sourceFolder === "string" && folderPathId(sourceFolder) === ref;
86495
- }
86496
-
86497
- // plugins/tasks/src/api.ts
86498
- var TASKS_FILE = "todo.json";
86499
- function requireString2(value, name) {
86500
- if (typeof value !== "string" || !value.trim()) {
86501
- throw apiError("bad-request", `Missing ${name} parameter`);
86502
- }
86503
- return value.trim();
86504
- }
86505
- function serializeTodoSource(document3, original) {
86506
- const indent = /^\{[\s\S]*?\n([ \t]+)"/m.exec(original)?.[1] ?? " ";
86507
- const trailing = original.endsWith(`
86508
- `) ? `
86509
- ` : "";
86510
- return `${JSON.stringify(document3, null, indent)}${trailing}`;
86511
- }
86512
- function toggleTaskSource(source, id) {
86513
- const parsed = parseTodoSource(source);
86514
- if (!parsed.ok) {
86515
- throw apiError("bad-request", todoIssueSummary(parsed.issues));
86516
- }
86517
- const tasks = Array.isArray(parsed.document.tasks) ? parsed.document.tasks : [];
86518
- const task = tasks.find((item) => item.id === id || item.key === id);
86519
- if (!task)
86520
- throw apiError("not-found", `Task not found: ${id}`);
86521
- const wasDone = task.done === true || task.status === "done";
86522
- const done = !wasDone;
86523
- const now = new Date().toISOString();
86524
- if (done) {
86525
- task.done = true;
86526
- task.status = "done";
86527
- task.resolution = "completed";
86528
- task.completedAt = now;
86529
- } else {
86530
- task.done = false;
86531
- task.status = "todo";
86532
- task.resolution = null;
86533
- task.completedAt = null;
86534
- }
86535
- task.updatedAt = now;
86536
- return { done, next: serializeTodoSource(parsed.document, source) };
86537
- }
86538
- async function findTaskSource(folder2, id) {
86539
- const search = async (paths2, target) => {
86540
- for (const path of paths2) {
86541
- const source = await readFile6(path, "utf8");
86542
- const parsed = parseTodoSource(source);
86543
- if (!parsed.ok)
86544
- continue;
86545
- const tasks = Array.isArray(parsed.document.tasks) ? parsed.document.tasks : [];
86546
- if (tasks.some((task) => task.id === target || task.key === target)) {
86547
- return { path, source };
86548
- }
86549
- }
86550
- return;
86551
- };
86552
- const visit = async (current) => {
86553
- const entries = await readdir4(current, { withFileTypes: true }).catch(() => []);
86554
- const paths2 = [];
86555
- for (const entry of entries) {
86556
- if (entry.isFile() && entry.name === TASKS_FILE) {
86557
- paths2.push(resolve9(current, entry.name));
86558
- continue;
86559
- }
86560
- if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules")
86561
- continue;
86562
- paths2.push(...await visit(resolve9(current, entry.name)));
86563
- }
86564
- return paths2;
86565
- };
86566
- const paths = (await visit(folder2)).sort((left, right) => left.localeCompare(right));
86567
- const qualified = parseQualifiedTaskId(id);
86568
- if (qualified) {
86569
- const owned = paths.filter((path) => refMatchesFolder(qualified.ref, dirname7(path)));
86570
- if (owned.length > 0) {
86571
- return await search(owned, qualified.id);
86572
- }
86573
- }
86574
- return await search(paths, qualified?.id ?? id);
86575
- }
86576
- var toggleTask = async ({ body, context, response }) => {
86577
- if (context.readOnly) {
86578
- throw apiError("forbidden", "Task updates are disabled for remote access");
86579
- }
86580
- const parsed = parseJsonBody(body);
86581
- const id = requireString2(parsed.id, "id");
86582
- const folder2 = context.authorizeFolder(typeof parsed.path === "string" && parsed.path ? parsed.path : null);
86583
- const found = await findTaskSource(folder2, id);
86584
- if (!found)
86585
- throw apiError("not-found", `Task not found: ${id}`);
86586
- const target = parseQualifiedTaskId(id)?.id ?? id;
86587
- const { done, next } = toggleTaskSource(found.source, target);
86588
- await writeFile3(found.path, next, "utf8");
86589
- context.watch(folder2);
86590
- jsonResponse(response, 200, { done, folder: folder2, id, ok: true });
86591
- };
86592
- var specByPath = {
86593
- "/api/tasks/toggle": {
86594
- method: "POST",
86595
- maxBodyBytes: 16 * 1024,
86596
- timeoutMs: 30000,
86597
- handle: toggleTask
86598
- }
86599
- };
86600
- async function handleTasksApiRoutes(request, response, context) {
86601
- const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
86602
- const spec = specByPath[url2.pathname];
86603
- if (!spec)
86604
- return false;
86605
- await runApiRoute(request, response, url2, spec, context);
86606
- return true;
86607
- }
86608
-
86609
84796
  // packages/session-finding/src/index.ts
86610
84797
  var import_local = __toESM(require_dist4(), 1);
86611
84798
  var import_sessions = __toESM(require_sessions(), 1);
@@ -86657,17 +84844,17 @@ function createSessionFinding(deps2) {
86657
84844
  var { listFolderSessions, readSessionTranscript } = createSessionFinding(deps);
86658
84845
 
86659
84846
  // packages/api/src/server/entity-folder.ts
86660
- var import_yaml4 = __toESM(require_dist(), 1);
86661
- import { readdir as readdir5, readFile as readFile7, realpath as realpath5, stat as stat3 } from "node:fs/promises";
86662
- import { homedir as homedir4 } from "node:os";
84847
+ var import_yaml3 = __toESM(require_dist(), 1);
84848
+ import { readdir as readdir3, readFile as readFile4, realpath as realpath3, stat as stat3 } from "node:fs/promises";
84849
+ import { homedir as homedir2 } from "node:os";
86663
84850
  import {
86664
84851
  basename as basename4,
86665
- dirname as dirname8,
84852
+ dirname as dirname5,
86666
84853
  extname as extname2,
86667
- isAbsolute as isAbsolute7,
84854
+ isAbsolute as isAbsolute4,
86668
84855
  join as join2,
86669
- relative as relative8,
86670
- resolve as resolve10
84856
+ relative as relative6,
84857
+ resolve as resolve6
86671
84858
  } from "node:path";
86672
84859
  var CONFIG_FILE = join2("kennel", "config.json");
86673
84860
  var IGNORED_DIRECTORIES = new Set([
@@ -86685,7 +84872,7 @@ function parseMarkdown(source) {
86685
84872
  ---`, 4);
86686
84873
  if (end === -1)
86687
84874
  return { body: source.trim(), frontmatter: {} };
86688
- const document3 = import_yaml4.default.parseDocument(source.slice(4, end));
84875
+ const document3 = import_yaml3.default.parseDocument(source.slice(4, end));
86689
84876
  const value = document3.errors.length === 0 ? document3.toJS() : {};
86690
84877
  return {
86691
84878
  body: source.slice(end + 4).trim(),
@@ -86710,11 +84897,11 @@ function paragraph(body) {
86710
84897
  }
86711
84898
  function configPath() {
86712
84899
  const xdg = process.env.XDG_CONFIG_HOME?.trim();
86713
- return xdg ? join2(xdg, CONFIG_FILE) : join2(homedir4(), ".config", CONFIG_FILE);
84900
+ return xdg ? join2(xdg, CONFIG_FILE) : join2(homedir2(), ".config", CONFIG_FILE);
86714
84901
  }
86715
84902
  async function readConfig(path) {
86716
84903
  try {
86717
- return parseConfig(await readFile7(path, "utf8"));
84904
+ return parseConfig(await readFile4(path, "utf8"));
86718
84905
  } catch (error51) {
86719
84906
  if (error51 && typeof error51 === "object" && "code" in error51 && error51.code === "ENOENT")
86720
84907
  return null;
@@ -86734,7 +84921,7 @@ function parseConfig(source) {
86734
84921
  if (!Array.isArray(roots))
86735
84922
  throw new Error("Invalid Kennel entity root config");
86736
84923
  const allowedRoots = roots.map((root) => {
86737
- if (typeof root !== "string" || !root.trim() || !isAbsolute7(root))
84924
+ if (typeof root !== "string" || !root.trim() || !isAbsolute4(root))
86738
84925
  throw new Error("Invalid Kennel entity root config");
86739
84926
  return root.trim();
86740
84927
  });
@@ -86747,21 +84934,21 @@ async function allowedRoots(currentWorkspaceRoot) {
86747
84934
  return currentWorkspaceRoot?.trim() ? [currentWorkspaceRoot] : [];
86748
84935
  }
86749
84936
  function inside4(root, path) {
86750
- const local = relative8(root, path);
86751
- return local === "" || !local.startsWith("..") && !isAbsolute7(local);
84937
+ const local = relative6(root, path);
84938
+ return local === "" || !local.startsWith("..") && !isAbsolute4(local);
86752
84939
  }
86753
84940
  async function normalizeRoot(rootPath, currentWorkspaceRoot) {
86754
84941
  const trimmed = rootPath?.trim();
86755
84942
  const fallback = currentWorkspaceRoot?.trim();
86756
84943
  if (!trimmed && !fallback)
86757
84944
  return null;
86758
- const requested = resolve10(trimmed || fallback);
86759
- const resolved = await realpath5(requested);
84945
+ const requested = resolve6(trimmed || fallback);
84946
+ const resolved = await realpath3(requested);
86760
84947
  const info = await stat3(resolved);
86761
84948
  if (!info.isDirectory())
86762
84949
  throw new Error(`${resolved} is not a folder`);
86763
84950
  const roots = await allowedRoots(currentWorkspaceRoot);
86764
- const realAllowedRoots = await Promise.all(roots.map((root) => realpath5(root)));
84951
+ const realAllowedRoots = await Promise.all(roots.map((root) => realpath3(root)));
86765
84952
  const allowed = realAllowedRoots.some((root) => inside4(root, resolved));
86766
84953
  if (!allowed)
86767
84954
  throw new Error("Entity root is outside the configured roots");
@@ -86770,7 +84957,7 @@ async function normalizeRoot(rootPath, currentWorkspaceRoot) {
86770
84957
  async function markdownFiles(folderPath) {
86771
84958
  const files = [];
86772
84959
  async function walk(currentPath) {
86773
- const entries = await readdir5(currentPath, { withFileTypes: true });
84960
+ const entries = await readdir3(currentPath, { withFileTypes: true });
86774
84961
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
86775
84962
  if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
86776
84963
  continue;
@@ -86801,24 +84988,24 @@ async function schemaPath(folderPath, rootPath) {
86801
84988
  }
86802
84989
  if (current === rootPath)
86803
84990
  return null;
86804
- current = dirname8(current);
84991
+ current = dirname5(current);
86805
84992
  }
86806
84993
  return null;
86807
84994
  }
86808
84995
  async function readEntity(folderPath, rootPath) {
86809
84996
  const indexPath = join2(folderPath, "index.md");
86810
- const index = parseMarkdown(await readFile7(indexPath, "utf8"));
84997
+ const index = parseMarkdown(await readFile4(indexPath, "utf8"));
86811
84998
  const files = await markdownFiles(folderPath);
86812
84999
  const documents = await Promise.all(files.map(async (filePath) => {
86813
- const document3 = parseMarkdown(await readFile7(filePath, "utf8"));
85000
+ const document3 = parseMarkdown(await readFile4(filePath, "utf8"));
86814
85001
  return {
86815
85002
  content: document3.body,
86816
- path: relative8(folderPath, filePath),
85003
+ path: relative6(folderPath, filePath),
86817
85004
  title: stringValue(document3.frontmatter.title) ?? heading(document3.body) ?? basename4(filePath, extname2(filePath))
86818
85005
  };
86819
85006
  }));
86820
85007
  const title = stringValue(index.frontmatter.title) ?? heading(index.body) ?? basename4(folderPath);
86821
- const relativePath = relative8(rootPath, folderPath) || ".";
85008
+ const relativePath = relative6(rootPath, folderPath) || ".";
86822
85009
  return {
86823
85010
  aliases: stringValues(index.frontmatter.aliases),
86824
85011
  content: [
@@ -86846,7 +85033,7 @@ async function discover(rootPath) {
86846
85033
  async function walk(folderPath) {
86847
85034
  if (await hasIndex(folderPath))
86848
85035
  folders.push(folderPath);
86849
- const entries = await readdir5(folderPath, { withFileTypes: true });
85036
+ const entries = await readdir3(folderPath, { withFileTypes: true });
86850
85037
  for (const entry of entries) {
86851
85038
  if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
86852
85039
  continue;
@@ -86890,20 +85077,20 @@ async function loadEntityWorkspace(root, query, selectedPath, currentWorkspaceRo
86890
85077
  // packages/api/src/server/api/document-edit.ts
86891
85078
  import { randomUUID as randomUUID2 } from "node:crypto";
86892
85079
  import {
86893
- readFile as readFile8,
86894
- realpath as realpath6,
86895
- rename as rename3,
85080
+ readFile as readFile5,
85081
+ realpath as realpath4,
85082
+ rename as rename2,
86896
85083
  stat as stat4,
86897
- unlink as unlink3,
86898
- writeFile as writeFile4
85084
+ unlink as unlink2,
85085
+ writeFile as writeFile2
86899
85086
  } from "node:fs/promises";
86900
85087
  import {
86901
- dirname as dirname9,
85088
+ dirname as dirname6,
86902
85089
  extname as extname3,
86903
- isAbsolute as isAbsolute8,
86904
- relative as relative9,
86905
- resolve as resolve11,
86906
- sep as sep7
85090
+ isAbsolute as isAbsolute5,
85091
+ relative as relative7,
85092
+ resolve as resolve7,
85093
+ sep as sep4
86907
85094
  } from "node:path";
86908
85095
 
86909
85096
  // packages/api/src/shared/selection.ts
@@ -86921,10 +85108,10 @@ async function editDocument(folder2, input) {
86921
85108
  throw apiError("bad-request", "Invalid document edit.");
86922
85109
  if (![".md", ".markdown"].includes(extname3(path).toLowerCase()))
86923
85110
  throw apiError("bad-request", "Only Markdown notes can be edited.");
86924
- const root = await realpath6(folder2);
86925
- const target = await realpath6(resolve11(root, path));
86926
- const local = relative9(root, target);
86927
- if (!local || local === ".." || local.startsWith(`..${sep7}`) || isAbsolute8(local))
85111
+ const root = await realpath4(folder2);
85112
+ const target = await realpath4(resolve7(root, path));
85113
+ const local = relative7(root, target);
85114
+ if (!local || local === ".." || local.startsWith(`..${sep4}`) || isAbsolute5(local))
86928
85115
  throw apiError("forbidden", "The document must be inside its folder.");
86929
85116
  let content;
86930
85117
  try {
@@ -86937,19 +85124,19 @@ async function editDocument(folder2, input) {
86937
85124
  const details = await stat4(target);
86938
85125
  if (!details.isFile() || details.nlink !== 1)
86939
85126
  throw apiError("forbidden", "Only regular, unlinked files can be edited.");
86940
- if (await readFile8(target, "utf8") !== original)
85127
+ if (await readFile5(target, "utf8") !== original)
86941
85128
  throw apiError("conflict", "This note changed on disk. Close this preview, reload the note, and try again.");
86942
- const temporary = resolve11(dirname9(target), `.kennel-edit-${randomUUID2()}.tmp`);
85129
+ const temporary = resolve7(dirname6(target), `.kennel-edit-${randomUUID2()}.tmp`);
86943
85130
  try {
86944
- await writeFile4(temporary, content, {
85131
+ await writeFile2(temporary, content, {
86945
85132
  flag: "wx",
86946
85133
  mode: details.mode & 511
86947
85134
  });
86948
- if (await readFile8(target, "utf8") !== original)
85135
+ if (await readFile5(target, "utf8") !== original)
86949
85136
  throw apiError("conflict", "This note changed on disk. Reload before applying an edit.");
86950
- await rename3(temporary, target);
85137
+ await rename2(temporary, target);
86951
85138
  } finally {
86952
- await unlink3(temporary).catch(() => {});
85139
+ await unlink2(temporary).catch(() => {});
86953
85140
  }
86954
85141
  return content;
86955
85142
  });
@@ -86970,7 +85157,7 @@ var documentEditRoute = {
86970
85157
  throw apiError("bad-request", "Invalid document edit.");
86971
85158
  const input = parsed.data;
86972
85159
  const folder2 = context.authorize(input.folder);
86973
- if (context.lockedFile && (typeof input.path !== "string" || await realpath6(resolve11(folder2, input.path)) !== context.lockedFile))
85160
+ if (context.lockedFile && (typeof input.path !== "string" || await realpath4(resolve7(folder2, input.path)) !== context.lockedFile))
86974
85161
  throw apiError("forbidden", "Only the selected document can be edited.");
86975
85162
  await editDocument(folder2, input);
86976
85163
  jsonResponse(response, 200, routes.documentEdit.response.parse({ saved: true }));
@@ -86978,8 +85165,8 @@ var documentEditRoute = {
86978
85165
  };
86979
85166
 
86980
85167
  // packages/api/src/server/api/create-agent-file.ts
86981
- import { writeFile as writeFile5 } from "node:fs/promises";
86982
- import { resolve as resolve12 } from "node:path";
85168
+ import { writeFile as writeFile3 } from "node:fs/promises";
85169
+ import { resolve as resolve8 } from "node:path";
86983
85170
  var createAgentFileRoute = {
86984
85171
  method: "POST",
86985
85172
  maxBodyBytes: 0,
@@ -86990,9 +85177,9 @@ var createAgentFileRoute = {
86990
85177
  } catch (error51) {
86991
85178
  throw apiError("not-found", error51 instanceof Error ? error51.message : "Folder not found");
86992
85179
  }
86993
- const target = resolve12(folder2, "AGENTS.md");
85180
+ const target = resolve8(folder2, "AGENTS.md");
86994
85181
  try {
86995
- await writeFile5(target, "", { flag: "wx" });
85182
+ await writeFile3(target, "", { flag: "wx" });
86996
85183
  } catch (error51) {
86997
85184
  if (error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST") {
86998
85185
  throw apiError("conflict", "AGENTS.md already exists in this folder");
@@ -87028,11 +85215,11 @@ var openPathRoute = {
87028
85215
  };
87029
85216
 
87030
85217
  // packages/api/src/server/folder-favicon.ts
87031
- import { readFile as readFile9 } from "node:fs/promises";
85218
+ import { readFile as readFile6 } from "node:fs/promises";
87032
85219
  import { join as join3 } from "node:path";
87033
85220
  async function folderWebsite(folder2) {
87034
85221
  for (const name of ["AGENTS.md", "README.md"]) {
87035
- const content = await readFile9(join3(folder2, name), "utf8").catch(() => "");
85222
+ const content = await readFile6(join3(folder2, name), "utf8").catch(() => "");
87036
85223
  const entry = markdownMetadata(content).entries.find(([key]) => key === "website");
87037
85224
  if (entry)
87038
85225
  return websiteHost(entry[1]);
@@ -87042,8 +85229,8 @@ async function folderWebsite(folder2) {
87042
85229
 
87043
85230
  // packages/api/src/server/folder-icons.ts
87044
85231
  var import_local2 = __toESM(require_dist4(), 1);
87045
- import { createHash as createHash6 } from "node:crypto";
87046
- import { readFile as readFile10, stat as stat5 } from "node:fs/promises";
85232
+ import { createHash as createHash4 } from "node:crypto";
85233
+ import { readFile as readFile7, stat as stat5 } from "node:fs/promises";
87047
85234
  import { extname as extname4 } from "node:path";
87048
85235
  var maxIconBytes2 = 4 * 1024 * 1024;
87049
85236
  var iconTypes = new Map([
@@ -87056,7 +85243,7 @@ var iconExtensions = new Map([
87056
85243
  ["image/png", "png"]
87057
85244
  ]);
87058
85245
  function assetKey(value) {
87059
- return createHash6("sha256").update(value).digest("hex");
85246
+ return createHash4("sha256").update(value).digest("hex");
87060
85247
  }
87061
85248
  function encodeBase64Url(value) {
87062
85249
  return Buffer.from(value, "utf8").toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
@@ -87073,7 +85260,7 @@ async function folderIcon(folder2, assets2, assetFiles, fallback) {
87073
85260
  if (!icon2 || !mediaType || !details?.size || details.size > maxIconBytes2) {
87074
85261
  return fallback ? await fallback(folder2, assets2) : undefined;
87075
85262
  }
87076
- const data = await readFile10(icon2);
85263
+ const data = await readFile7(icon2);
87077
85264
  const hash3 = assetKey(data);
87078
85265
  const key = `local:${hash3}`;
87079
85266
  assetFiles.set(hash3, { mediaType, path: icon2 });
@@ -87094,8 +85281,8 @@ async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets2 = {}) {
87094
85281
 
87095
85282
  // packages/api/src/server/plugins.ts
87096
85283
  import { pathToFileURL } from "node:url";
87097
- import { existsSync as existsSync4 } from "node:fs";
87098
- import { dirname as dirname14, resolve as resolve17, sep as sep11 } from "node:path";
85284
+ import { existsSync as existsSync3 } from "node:fs";
85285
+ import { dirname as dirname8, resolve as resolve11, sep as sep6 } from "node:path";
87099
85286
 
87100
85287
  // packages/agents/src/manifest.ts
87101
85288
  var agentsManifest = {
@@ -87104,9 +85291,9 @@ var agentsManifest = {
87104
85291
  };
87105
85292
 
87106
85293
  // packages/agents/src/node.ts
87107
- import { lstat as lstat2, readFile as readFile11, realpath as realpath7, stat as stat6 } from "node:fs/promises";
87108
- import { homedir as homedir5 } from "node:os";
87109
- import { basename as basename5, dirname as dirname10, resolve as resolve13 } from "node:path";
85294
+ import { lstat, readFile as readFile8, realpath as realpath5, stat as stat6 } from "node:fs/promises";
85295
+ import { homedir as homedir3 } from "node:os";
85296
+ import { basename as basename5, dirname as dirname7, resolve as resolve9 } from "node:path";
87110
85297
 
87111
85298
  // packages/agents/src/profiles.ts
87112
85299
  var CLIENT_ORDER = [
@@ -87250,13 +85437,13 @@ function sortSources(sources) {
87250
85437
  // packages/agents/src/node.ts
87251
85438
  async function canonicalize(path) {
87252
85439
  try {
87253
- return await realpath7(path);
85440
+ return await realpath5(path);
87254
85441
  } catch {
87255
85442
  return null;
87256
85443
  }
87257
85444
  }
87258
85445
  async function hasGit(folder2) {
87259
- const entry = await lstat2(resolve13(folder2, ".git")).catch(() => {
85446
+ const entry = await lstat(resolve9(folder2, ".git")).catch(() => {
87260
85447
  return;
87261
85448
  });
87262
85449
  return Boolean(entry);
@@ -87266,7 +85453,7 @@ async function nearestGitRoot(folder2) {
87266
85453
  while (true) {
87267
85454
  if (await hasGit(current))
87268
85455
  return current;
87269
- const parent = dirname10(current);
85456
+ const parent = dirname7(current);
87270
85457
  if (parent === current)
87271
85458
  return null;
87272
85459
  current = parent;
@@ -87275,12 +85462,12 @@ async function nearestGitRoot(folder2) {
87275
85462
  async function nearestRaggleRoot(folder2) {
87276
85463
  let current = folder2;
87277
85464
  while (true) {
87278
- const entry = await lstat2(resolve13(current, "raggle.json")).catch(() => {
85465
+ const entry = await lstat(resolve9(current, "raggle.json")).catch(() => {
87279
85466
  return;
87280
85467
  });
87281
85468
  if (entry)
87282
85469
  return current;
87283
- const parent = dirname10(current);
85470
+ const parent = dirname7(current);
87284
85471
  if (parent === current)
87285
85472
  return null;
87286
85473
  current = parent;
@@ -87291,7 +85478,7 @@ async function ancestorBoundary(folder2, scope) {
87291
85478
  while (true) {
87292
85479
  if (scope === "git" && await hasGit(current))
87293
85480
  return current;
87294
- const parent = dirname10(current);
85481
+ const parent = dirname7(current);
87295
85482
  if (parent === current)
87296
85483
  return current;
87297
85484
  current = parent;
@@ -87304,7 +85491,7 @@ function ancestorChain(folder2, boundary) {
87304
85491
  chain.push(current);
87305
85492
  if (current === boundary)
87306
85493
  break;
87307
- const parent = dirname10(current);
85494
+ const parent = dirname7(current);
87308
85495
  if (parent === current)
87309
85496
  break;
87310
85497
  current = parent;
@@ -87352,16 +85539,16 @@ function mergeChains(chains) {
87352
85539
  async function selectFiles(directory, candidates, mode) {
87353
85540
  if (mode === "all") {
87354
85541
  const found = [];
87355
- for (const relative10 of candidates) {
87356
- if (await isRegularFile(resolve13(directory, relative10))) {
87357
- found.push({ filename: basename5(relative10), relative: relative10 });
85542
+ for (const relative8 of candidates) {
85543
+ if (await isRegularFile(resolve9(directory, relative8))) {
85544
+ found.push({ filename: basename5(relative8), relative: relative8 });
87358
85545
  }
87359
85546
  }
87360
85547
  return found;
87361
85548
  }
87362
- for (const relative10 of candidates) {
87363
- if (await isRegularFile(resolve13(directory, relative10))) {
87364
- return [{ filename: basename5(relative10), relative: relative10 }];
85549
+ for (const relative8 of candidates) {
85550
+ if (await isRegularFile(resolve9(directory, relative8))) {
85551
+ return [{ filename: basename5(relative8), relative: relative8 }];
87365
85552
  }
87366
85553
  }
87367
85554
  return [];
@@ -87370,7 +85557,7 @@ function expandHome(path, homeDir) {
87370
85557
  if (path === "~")
87371
85558
  return homeDir;
87372
85559
  if (path.startsWith("~/"))
87373
- return resolve13(homeDir, path.slice(2));
85560
+ return resolve9(homeDir, path.slice(2));
87374
85561
  return path;
87375
85562
  }
87376
85563
  function globalDirectory(profile, homeDir) {
@@ -87381,7 +85568,7 @@ function globalDirectory(profile, homeDir) {
87381
85568
  return expandHome(envValue, homeDir);
87382
85569
  if (profile.globalDir === "")
87383
85570
  return homeDir;
87384
- return resolve13(homeDir, profile.globalDir);
85571
+ return resolve9(homeDir, profile.globalDir);
87385
85572
  }
87386
85573
  function readError(error51) {
87387
85574
  return error51 instanceof Error ? error51.message : "Could not read file";
@@ -87389,7 +85576,7 @@ function readError(error51) {
87389
85576
  var INSTRUCTION_NAMES = new Set(Object.values(PROFILES).flatMap((profile) => [...profile.files, ...profile.globalFiles]).map((path) => basename5(path).toLowerCase()));
87390
85577
  async function codexModelInstructionsFile(codexHome) {
87391
85578
  try {
87392
- const content = await readFile11(resolve13(codexHome, "config.toml"), "utf8");
85579
+ const content = await readFile8(resolve9(codexHome, "config.toml"), "utf8");
87393
85580
  const match = /^\s*model_instructions_file\s*=\s*["']?([^"'\n]+)["']?\s*$/m.exec(content);
87394
85581
  return match?.[1]?.trim() ?? null;
87395
85582
  } catch {
@@ -87404,7 +85591,7 @@ async function loadSource(input) {
87404
85591
  path: input.literalPath,
87405
85592
  sourcePaths: [input.literalPath],
87406
85593
  filename: input.filename,
87407
- directory: dirname10(input.literalPath),
85594
+ directory: dirname7(input.literalPath),
87408
85595
  level: input.level,
87409
85596
  status: "unreadable",
87410
85597
  text: "",
@@ -87420,7 +85607,7 @@ async function loadSource(input) {
87420
85607
  path: real,
87421
85608
  sourcePaths: [input.literalPath],
87422
85609
  filename: input.filename,
87423
- directory: dirname10(real),
85610
+ directory: dirname7(real),
87424
85611
  level: input.level,
87425
85612
  status: "loaded",
87426
85613
  text: "",
@@ -87445,7 +85632,7 @@ async function loadSource(input) {
87445
85632
  record2.size = details.size;
87446
85633
  if (input.readText) {
87447
85634
  try {
87448
- record2.text = await readFile11(real, "utf8");
85635
+ record2.text = await readFile8(real, "utf8");
87449
85636
  } catch (error51) {
87450
85637
  record2.status = "unreadable";
87451
85638
  record2.reason = readError(error51);
@@ -87454,7 +85641,7 @@ async function loadSource(input) {
87454
85641
  return record2;
87455
85642
  }
87456
85643
  async function resolveAgents(options) {
87457
- const homeDir = options.homeDir ?? homedir5();
85644
+ const homeDir = options.homeDir ?? homedir3();
87458
85645
  const client = options.client ?? "all";
87459
85646
  const readText = options.readText ?? true;
87460
85647
  const folder2 = await canonicalize(options.folder);
@@ -87487,7 +85674,7 @@ async function resolveAgents(options) {
87487
85674
  clientId: profileId,
87488
85675
  filename: file3.filename,
87489
85676
  level,
87490
- literalPath: resolve13(directory, file3.relative),
85677
+ literalPath: resolve9(directory, file3.relative),
87491
85678
  override: file3.filename === "AGENTS.override.md",
87492
85679
  readText
87493
85680
  });
@@ -87510,7 +85697,7 @@ async function resolveAgents(options) {
87510
85697
  clientId: profileId,
87511
85698
  filename: file3.filename,
87512
85699
  level: "linked",
87513
- literalPath: resolve13(canonicalLinked, file3.relative),
85700
+ literalPath: resolve9(canonicalLinked, file3.relative),
87514
85701
  override: file3.filename === "AGENTS.override.md",
87515
85702
  readText
87516
85703
  });
@@ -87525,7 +85712,7 @@ async function resolveAgents(options) {
87525
85712
  clientId: profileId,
87526
85713
  filename: file3.filename,
87527
85714
  level: "global",
87528
- literalPath: resolve13(globalDir, file3.relative),
85715
+ literalPath: resolve9(globalDir, file3.relative),
87529
85716
  override: file3.filename === "AGENTS.override.md",
87530
85717
  readText
87531
85718
  });
@@ -87608,559 +85795,9 @@ function createAgentsPlugin(options = {}) {
87608
85795
  };
87609
85796
  }
87610
85797
 
87611
- // plugins/calendar/src/server.ts
87612
- import { readdir as readdir6, readFile as readFile12 } from "node:fs/promises";
87613
- import { basename as basename6, dirname as dirname11, relative as relative10, resolve as resolve14, sep as sep8 } from "node:path";
87614
-
87615
- // plugins/calendar/src/manifest.ts
87616
- var calendarManifest = {
87617
- id: "calendar",
87618
- label: "Calendar"
87619
- };
87620
-
87621
- // node_modules/.bun/date-fns@4.1.0/node_modules/date-fns/constants.js
87622
- var daysInYear = 365.2425;
87623
- var maxTime = Math.pow(10, 8) * 24 * 60 * 60 * 1000;
87624
- var minTime = -maxTime;
87625
- var secondsInHour = 3600;
87626
- var secondsInDay = secondsInHour * 24;
87627
- var secondsInWeek = secondsInDay * 7;
87628
- var secondsInYear = secondsInDay * daysInYear;
87629
- var secondsInMonth = secondsInYear / 12;
87630
- var secondsInQuarter = secondsInMonth * 3;
87631
- var constructFromSymbol = Symbol.for("constructDateFrom");
87632
-
87633
- // node_modules/.bun/date-fns@4.1.0/node_modules/date-fns/constructFrom.js
87634
- function constructFrom(date5, value) {
87635
- if (typeof date5 === "function")
87636
- return date5(value);
87637
- if (date5 && typeof date5 === "object" && constructFromSymbol in date5)
87638
- return date5[constructFromSymbol](value);
87639
- if (date5 instanceof Date)
87640
- return new date5.constructor(value);
87641
- return new Date(value);
87642
- }
87643
-
87644
- // node_modules/.bun/date-fns@4.1.0/node_modules/date-fns/toDate.js
87645
- function toDate(argument, context) {
87646
- return constructFrom(context || argument, argument);
87647
- }
87648
-
87649
- // node_modules/.bun/date-fns@4.1.0/node_modules/date-fns/endOfDay.js
87650
- function endOfDay(date5, options) {
87651
- const _date2 = toDate(date5, options?.in);
87652
- _date2.setHours(23, 59, 59, 999);
87653
- return _date2;
87654
- }
87655
-
87656
- // plugins/calendar/src/parse.ts
87657
- var colors = new Set([
87658
- "blue",
87659
- "green",
87660
- "red",
87661
- "yellow",
87662
- "purple",
87663
- "orange"
87664
- ]);
87665
- var repeats = new Set(["daily", "weekly", "monthly"]);
87666
- var statuses = new Set([
87667
- "confirmed",
87668
- "tentative",
87669
- "cancelled"
87670
- ]);
87671
- function valueAt(record2, keys) {
87672
- for (const key of keys) {
87673
- const value = record2[key];
87674
- if (value !== undefined && value !== null && value !== "")
87675
- return value;
87676
- }
87677
- return;
87678
- }
87679
- function text2(record2, keys) {
87680
- const value = valueAt(record2, keys);
87681
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
87682
- }
87683
- function texts(value) {
87684
- if (!Array.isArray(value))
87685
- return;
87686
- const values = [
87687
- ...new Set(value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean))
87688
- ];
87689
- return values.length ? values : undefined;
87690
- }
87691
- function numbers(value) {
87692
- if (!Array.isArray(value))
87693
- return;
87694
- const values = [
87695
- ...new Set(value.filter((item) => typeof item === "number" && Number.isInteger(item) && item >= 0))
87696
- ].sort((left, right) => left - right);
87697
- return values.length ? values : undefined;
87698
- }
87699
- var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
87700
- function localDateOnly(value) {
87701
- const trimmed = value.trim();
87702
- if (!DATE_ONLY.test(trimmed))
87703
- return;
87704
- const [year, month, day] = trimmed.split("-").map(Number);
87705
- return new Date(year ?? 0, (month ?? 1) - 1, day ?? 1);
87706
- }
87707
- function date5(value, fallback) {
87708
- if (value instanceof Date && !Number.isNaN(value.getTime()))
87709
- return value;
87710
- if (typeof value === "number" && Number.isFinite(value))
87711
- return new Date(value);
87712
- if (typeof value === "string" && value.trim()) {
87713
- const parsed = localDateOnly(value) ?? new Date(value);
87714
- if (!Number.isNaN(parsed.getTime()))
87715
- return parsed;
87716
- }
87717
- return fallback;
87718
- }
87719
- function color(value) {
87720
- if (typeof value !== "string")
87721
- return;
87722
- const candidate = value.toLowerCase();
87723
- return colors.has(candidate) ? candidate : undefined;
87724
- }
87725
- function repeating(value) {
87726
- if (typeof value !== "string")
87727
- return;
87728
- const candidate = value.toLowerCase();
87729
- return repeats.has(candidate) ? candidate : undefined;
87730
- }
87731
- function status(value) {
87732
- if (typeof value !== "string")
87733
- return;
87734
- const candidate = value.toLowerCase();
87735
- return statuses.has(candidate) ? candidate : undefined;
87736
- }
87737
- function url2(value) {
87738
- if (typeof value !== "string" || !value.trim())
87739
- return;
87740
- const candidate = value.trim();
87741
- return /^https?:\/\/[^\s]+$/i.test(candidate) ? candidate : undefined;
87742
- }
87743
- function dates(value) {
87744
- if (!Array.isArray(value))
87745
- return;
87746
- const values = value.map((item) => date5(item, new Date(Number.NaN))).filter((item) => !Number.isNaN(item.getTime()));
87747
- return values.length ? values : undefined;
87748
- }
87749
- function until(value) {
87750
- if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
87751
- const parsed2 = date5(value, new Date(Number.NaN));
87752
- return Number.isNaN(parsed2.getTime()) ? undefined : endOfDay(parsed2);
87753
- }
87754
- const parsed = date5(value, new Date(Number.NaN));
87755
- return Number.isNaN(parsed.getTime()) ? undefined : parsed;
87756
- }
87757
- function parseCalendar(calendar) {
87758
- if (Array.isArray(calendar))
87759
- return { events: parseEvents(calendar) };
87760
- if (!calendar || typeof calendar !== "object")
87761
- return;
87762
- const record2 = calendar;
87763
- if (!Array.isArray(record2.events))
87764
- return;
87765
- return {
87766
- color: color(record2.color),
87767
- startDate: typeof record2.startDate === "string" && record2.startDate.trim() ? record2.startDate.trim() : undefined,
87768
- group: typeof record2.group === "string" && record2.group.trim() ? record2.group.trim() : undefined,
87769
- title: typeof record2.title === "string" && record2.title.trim() ? record2.title.trim() : undefined,
87770
- events: parseEvents(record2.events)
87771
- };
87772
- }
87773
- function parseCalendarJson(source) {
87774
- try {
87775
- return parseCalendar(JSON.parse(source));
87776
- } catch {
87777
- return;
87778
- }
87779
- }
87780
- function parseEvents(events) {
87781
- return events.map((record2, index) => normalizeEvent(record2, index));
87782
- }
87783
- function normalizeEvent(record2, index) {
87784
- const id = valueAt(record2, ["id", "uid", "key"])?.toString() ?? String(index);
87785
- const startValue = date5(valueAt(record2, ["start", "startDate", "start_date", "date", "begin"]), new Date(0));
87786
- const endValue = date5(valueAt(record2, ["end", "endDate", "end_date", "due", "finish"]), startValue);
87787
- const allDay = typeof record2.allDay === "boolean" ? record2.allDay : undefined;
87788
- const start = startValue;
87789
- const end = endValue > start ? endValue : new Date(start.getTime() + (allDay ? 86400000 : 3600000));
87790
- const rawColor = valueAt(record2, [
87791
- "color",
87792
- "colour",
87793
- "calendarId",
87794
- "calendar",
87795
- "category"
87796
- ]) ?? valueAt(record2, ["calendars"]);
87797
- const eventColor = color(rawColor) ?? color(Array.isArray(rawColor) ? rawColor[0] : undefined);
87798
- return {
87799
- id,
87800
- start,
87801
- end,
87802
- color: eventColor,
87803
- folder: typeof record2.folder === "string" ? record2.folder : undefined,
87804
- group: text2(record2, ["group"]),
87805
- groupColor: color(record2.groupColor),
87806
- repeating: repeating(record2.repeating),
87807
- allDay,
87808
- attendees: texts(record2.attendees),
87809
- excludeDates: dates(record2.excludeDates),
87810
- location: text2(record2, ["location"]),
87811
- organizer: text2(record2, ["organizer"]),
87812
- reminders: numbers(record2.reminders),
87813
- repeatUntil: until(record2.repeatUntil),
87814
- status: status(record2.status),
87815
- tags: texts(record2.tags),
87816
- url: url2(record2.url),
87817
- description: text2(record2, [
87818
- "description",
87819
- "detail",
87820
- "notes",
87821
- "summary",
87822
- "body"
87823
- ]),
87824
- title: text2(record2, ["title", "name", "subject", "summary", "label"]) ?? "Untitled"
87825
- };
87826
- }
87827
- function mergeCalendars(sources) {
87828
- const root = sources.find((source) => source.path === "calendar.json");
87829
- return {
87830
- title: root?.calendar.title ?? sources[0]?.calendar.title,
87831
- startDate: root?.calendar.startDate ?? sources[0]?.calendar.startDate,
87832
- events: sources.flatMap((source) => {
87833
- const group = source.calendar.group ?? source.group;
87834
- return source.calendar.events.map((event) => ({
87835
- ...event,
87836
- folder: source.folder,
87837
- group: event.group ?? group,
87838
- groupColor: event.groupColor ?? source.calendar.color,
87839
- id: `${source.path}:${event.id}`
87840
- }));
87841
- })
87842
- };
87843
- }
87844
- function eventCount(calendar) {
87845
- return calendar?.events.length ?? 0;
87846
- }
87847
- var EVENT_TITLE_KEYS = [
87848
- "title",
87849
- "name",
87850
- "subject",
87851
- "summary",
87852
- "label"
87853
- ];
87854
- var EVENT_START_KEYS = [
87855
- "start",
87856
- "startDate",
87857
- "start_date",
87858
- "date",
87859
- "begin"
87860
- ];
87861
- var EVENT_END_KEYS = ["end", "endDate", "end_date", "due", "finish"];
87862
- function isRecord2(value) {
87863
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
87864
- }
87865
- function strictDate(value) {
87866
- if (value instanceof Date && !Number.isNaN(value.getTime()))
87867
- return value;
87868
- if (typeof value === "number" && Number.isFinite(value))
87869
- return new Date(value);
87870
- if (typeof value === "string" && value.trim()) {
87871
- const parsed = localDateOnly(value) ?? new Date(value);
87872
- if (!Number.isNaN(parsed.getTime()))
87873
- return parsed;
87874
- }
87875
- return;
87876
- }
87877
- function validateCalendarFile(value) {
87878
- if (Array.isArray(value)) {
87879
- return value.flatMap((event, index) => validateEvent(event, `events[${index}]`));
87880
- }
87881
- if (!isRecord2(value)) {
87882
- return [
87883
- {
87884
- path: "#",
87885
- message: "calendar.json must contain an object or an array of events"
87886
- }
87887
- ];
87888
- }
87889
- const issues = [];
87890
- if (!Array.isArray(value.events)) {
87891
- issues.push({ path: "events", message: "must be an array of events" });
87892
- } else {
87893
- value.events.forEach((event, index) => {
87894
- issues.push(...validateEvent(event, `events[${index}]`));
87895
- });
87896
- }
87897
- return issues;
87898
- }
87899
- function validateEvent(event, path) {
87900
- if (!isRecord2(event)) {
87901
- return [{ path, message: "must be an event object" }];
87902
- }
87903
- const issues = [];
87904
- const hasTitle = EVENT_TITLE_KEYS.some((key) => typeof event[key] === "string" && event[key].trim().length > 0);
87905
- if (!hasTitle) {
87906
- issues.push({
87907
- path,
87908
- message: "has no title (expected one of title, name, subject, summary, label)"
87909
- });
87910
- }
87911
- const startValue = EVENT_START_KEYS.find((key) => event[key] !== undefined);
87912
- if (startValue !== undefined && !strictDate(event[startValue])) {
87913
- issues.push({
87914
- path: `${path}.${startValue}`,
87915
- message: "could not be parsed as a date"
87916
- });
87917
- }
87918
- const endValue = EVENT_END_KEYS.find((key) => event[key] !== undefined);
87919
- if (endValue !== undefined && event[endValue] !== null && !strictDate(event[endValue])) {
87920
- issues.push({
87921
- path: `${path}.${endValue}`,
87922
- message: "could not be parsed as a date"
87923
- });
87924
- }
87925
- if (event.allDay !== undefined && typeof event.allDay !== "boolean") {
87926
- issues.push({ path: `${path}.allDay`, message: "must be a boolean" });
87927
- }
87928
- if (event.repeating !== undefined && !["daily", "weekly", "monthly"].includes(String(event.repeating))) {
87929
- issues.push({
87930
- path: `${path}.repeating`,
87931
- message: "must be one of daily, weekly, monthly"
87932
- });
87933
- }
87934
- if (event.status !== undefined && !["confirmed", "tentative", "cancelled"].includes(String(event.status))) {
87935
- issues.push({
87936
- path: `${path}.status`,
87937
- message: "must be one of confirmed, tentative, cancelled"
87938
- });
87939
- }
87940
- if (event.excludeDates !== undefined && !Array.isArray(event.excludeDates)) {
87941
- issues.push({
87942
- path: `${path}.excludeDates`,
87943
- message: "must be an array of dates"
87944
- });
87945
- }
87946
- return issues;
87947
- }
87948
- function parseCalendarSource(source) {
87949
- return parseJsonSource(source, validateCalendarFile);
87950
- }
87951
- function calendarIssueSummary(issues) {
87952
- return issueSummary(issues, "calendar.json");
87953
- }
87954
- function starterCalendarSource() {
87955
- return `${JSON.stringify({
87956
- color: "blue",
87957
- events: [
87958
- {
87959
- id: "1",
87960
- title: "First event",
87961
- description: "Describe what happens.",
87962
- start: "2030-01-01T09:00:00",
87963
- end: "2030-01-01T10:00:00",
87964
- allDay: false,
87965
- status: "confirmed",
87966
- attendees: []
87967
- }
87968
- ]
87969
- }, null, 2)}
87970
- `;
87971
- }
87972
-
87973
- // plugins/calendar/src/server.ts
87974
- async function loadSources(folder2) {
87975
- const visit = async (current) => {
87976
- const entries = await readdir6(current, { withFileTypes: true }).catch(() => []);
87977
- const paths2 = [];
87978
- for (const entry of entries) {
87979
- if (entry.isFile() && entry.name === "calendar.json") {
87980
- paths2.push(resolve14(current, entry.name));
87981
- continue;
87982
- }
87983
- if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules")
87984
- continue;
87985
- paths2.push(...await visit(resolve14(current, entry.name)));
87986
- }
87987
- return paths2;
87988
- };
87989
- const paths = (await visit(folder2)).sort((left, right) => relative10(folder2, left).localeCompare(relative10(folder2, right)));
87990
- const issues = [];
87991
- const sources = [];
87992
- await Promise.all(paths.map(async (path) => {
87993
- const sourceText = await readFile12(path, "utf8");
87994
- const file3 = relative10(folder2, path).split(sep8).join("/");
87995
- const validation = parseCalendarSource(sourceText);
87996
- if (!validation.ok) {
87997
- issues.push({ file: file3, problems: validation.issues });
87998
- return;
87999
- }
88000
- const calendar = parseCalendarJson(sourceText);
88001
- if (!calendar) {
88002
- issues.push({
88003
- file: file3,
88004
- problems: [{ path: "#", message: "could not be read as a calendar" }]
88005
- });
88006
- return;
88007
- }
88008
- const directory = dirname11(file3);
88009
- sources.push({
88010
- calendar,
88011
- folder: dirname11(path),
88012
- group: directory === "." ? basename6(folder2) : directory,
88013
- path: file3
88014
- });
88015
- }));
88016
- issues.sort((left, right) => left.file.localeCompare(right.file));
88017
- return { issues, sources };
88018
- }
88019
- function flattenIssues(load) {
88020
- return load.issues.flatMap(({ file: file3, problems }) => problems.map((problem) => ({
88021
- path: problem.path === "#" ? file3 : `${file3}: ${problem.path}`,
88022
- message: problem.message
88023
- })));
88024
- }
88025
- function detectionFor(load) {
88026
- const noContent = load.sources.length === 0 && load.issues.length === 0;
88027
- if (noContent)
88028
- return { status: "unavailable" };
88029
- if (load.issues.length > 0) {
88030
- return {
88031
- status: "error",
88032
- error: calendarIssueSummary(flattenIssues(load))
88033
- };
88034
- }
88035
- return {
88036
- count: eventCount(mergeCalendars(load.sources)),
88037
- status: "available"
88038
- };
88039
- }
88040
- function createCalendarPlugin() {
88041
- return {
88042
- ...calendarManifest,
88043
- async detect(context) {
88044
- return detectionFor(await loadSources(context.folder));
88045
- },
88046
- async handle(context, subpath) {
88047
- if (subpath !== "")
88048
- return { kind: "unavailable" };
88049
- const load = await loadSources(context.folder);
88050
- const detection = detectionFor(load);
88051
- if (detection.status === "unavailable")
88052
- return { kind: "unavailable" };
88053
- const calendar = mergeCalendars(load.sources);
88054
- const issues = flattenIssues(load);
88055
- return {
88056
- kind: "available",
88057
- data: {
88058
- calendar: {
88059
- count: calendar.events.length,
88060
- items: calendar.events,
88061
- startDate: calendar.startDate,
88062
- title: calendar.title
88063
- },
88064
- folder: context.folder,
88065
- issues: issues.length > 0 ? issues : undefined,
88066
- status: issues.length > 0 ? "error" : "available"
88067
- }
88068
- };
88069
- },
88070
- async init(context) {
88071
- return {
88072
- files: [{ path: "calendar.json", content: starterCalendarSource() }]
88073
- };
88074
- }
88075
- };
88076
- }
88077
-
88078
- // plugins/mail/src/manifest.ts
88079
- var mailManifest = {
88080
- id: "mail",
88081
- label: "Mail"
88082
- };
88083
-
88084
- // plugins/mail/src/server.ts
88085
- function detailData(mail, preview) {
88086
- return {
88087
- kind: "available",
88088
- data: {
88089
- mail: {
88090
- ...mail,
88091
- bcc: mail.bcc,
88092
- body: mail.body,
88093
- cc: mail.cc,
88094
- createdAt: mail.createdAt,
88095
- filePath: mail.filePath,
88096
- folder: mail.folder,
88097
- id: mail.id,
88098
- status: mail.status,
88099
- subject: mail.subject,
88100
- tags: mail.tags,
88101
- to: mail.to,
88102
- updatedAt: mail.updatedAt,
88103
- noSignature: mail.noSignature
88104
- },
88105
- preview
88106
- }
88107
- };
88108
- }
88109
- function createMailPlugin({
88110
- mailDir = defaultMailDir(),
88111
- previewer
88112
- } = {}) {
88113
- const mailos = createMailosService();
88114
- const compose = previewer ?? (async ({ mail }) => ({
88115
- body: mail.body,
88116
- signature: mail.noSignature ? null : mail.folder ? await mailos.signature(mail.folder) : null
88117
- }));
88118
- return {
88119
- ...mailManifest,
88120
- async detect(context) {
88121
- const items2 = await listMailForFolder({ folder: context.folder, mailDir });
88122
- if (!items2.length)
88123
- return { status: "unavailable" };
88124
- return { count: items2.length, status: "available" };
88125
- },
88126
- async handle(context, subpath) {
88127
- const folder2 = context.folder;
88128
- if (subpath === "") {
88129
- const items2 = await listMailForFolder({ folder: folder2, mailDir });
88130
- if (!items2.length) {
88131
- return {
88132
- kind: "available",
88133
- data: { folder: folder2, items: [], status: "unavailable" }
88134
- };
88135
- }
88136
- return {
88137
- kind: "available",
88138
- data: { folder: folder2, items: items2, status: "available" }
88139
- };
88140
- }
88141
- const id = subpath.trim();
88142
- const mail = await readMailById(id, mailDir);
88143
- if (!mail)
88144
- throw new Error("Mail not found");
88145
- if (!mail.folder)
88146
- throw new Error("Mail has no folder");
88147
- if (!isFolderWithin(folder2, mail.folder))
88148
- throw new Error("Mail not found for this folder");
88149
- let preview;
88150
- try {
88151
- preview = await compose({ folder: folder2, id: mail.id, mail });
88152
- } catch (error51) {
88153
- const message = error51 instanceof Error ? error51.message : "Unknown preview error";
88154
- throw new FolderPluginFailure(`Mail preview failed: ${message}`);
88155
- }
88156
- return detailData(mail, preview);
88157
- }
88158
- };
88159
- }
88160
-
88161
85798
  // packages/notes/src/server.ts
88162
- import { readdir as readdir7, stat as stat7 } from "node:fs/promises";
88163
- import { basename as basename7, extname as extname5, relative as relative11, resolve as resolve15, sep as sep9 } from "node:path";
85799
+ import { readdir as readdir4, stat as stat7 } from "node:fs/promises";
85800
+ import { basename as basename6, extname as extname5, relative as relative8, resolve as resolve10, sep as sep5 } from "node:path";
88164
85801
 
88165
85802
  // packages/notes/src/manifest.ts
88166
85803
  var notesManifest = {
@@ -88173,11 +85810,11 @@ var markdownExtensions = new Set([".md", ".markdown"]);
88173
85810
  async function listMarkdownFiles(folder2) {
88174
85811
  const files = [];
88175
85812
  const visit = async (current) => {
88176
- const entries = await readdir7(current, { withFileTypes: true }).catch(() => []);
85813
+ const entries = await readdir4(current, { withFileTypes: true }).catch(() => []);
88177
85814
  for (const entry of entries) {
88178
85815
  if (entry.name === "node_modules" || entry.name.startsWith("."))
88179
85816
  continue;
88180
- const path = resolve15(current, entry.name);
85817
+ const path = resolve10(current, entry.name);
88181
85818
  if (entry.isDirectory()) {
88182
85819
  await visit(path);
88183
85820
  continue;
@@ -88190,8 +85827,8 @@ async function listMarkdownFiles(folder2) {
88190
85827
  });
88191
85828
  files.push({
88192
85829
  activityAt: details?.mtimeMs ?? 0,
88193
- name: basename7(entry.name, extname5(entry.name)),
88194
- relativePath: relative11(folder2, path).split(sep9).join("/")
85830
+ name: basename6(entry.name, extname5(entry.name)),
85831
+ relativePath: relative8(folder2, path).split(sep5).join("/")
88195
85832
  });
88196
85833
  }
88197
85834
  };
@@ -88233,505 +85870,6 @@ function createNotesPlugin() {
88233
85870
  };
88234
85871
  }
88235
85872
 
88236
- // plugins/projects/src/server.ts
88237
- import { execFile as execFile2 } from "node:child_process";
88238
- import { existsSync as existsSync3 } from "node:fs";
88239
- import { createRequire as createRequire2 } from "node:module";
88240
- import { dirname as dirname12, join as join4 } from "node:path";
88241
- import { fileURLToPath } from "node:url";
88242
-
88243
- // plugins/projects/src/manifest.ts
88244
- var projectsManifest = {
88245
- id: "projects",
88246
- label: "Projects"
88247
- };
88248
-
88249
- // plugins/projects/src/server.ts
88250
- var scoutScript = `
88251
- const local = require(process.argv[1]);
88252
- (async () => {
88253
- const folder = process.argv[2];
88254
- const connected = await local.listConnectedProjects(folder, {
88255
- depth: 1,
88256
- focused: true,
88257
- });
88258
- process.stdout.write(JSON.stringify(local.connectedProjectsList(folder, connected)));
88259
- })().catch(error => { console.error(error); process.exitCode = 1; });
88260
- `;
88261
- function resolveScoutModule() {
88262
- const require2 = createRequire2(import.meta.url);
88263
- const candidates = [];
88264
- try {
88265
- candidates.push(require2.resolve("@raggle-ai/local"));
88266
- } catch {}
88267
- let directory = dirname12(fileURLToPath(import.meta.url));
88268
- while (dirname12(directory) !== directory) {
88269
- if (existsSync3(join4(directory, "raggle.json"))) {
88270
- candidates.push(join4(dirname12(directory), "raggle-ai-scout", "packages", "local"));
88271
- break;
88272
- }
88273
- directory = dirname12(directory);
88274
- }
88275
- for (const candidate of candidates) {
88276
- try {
88277
- const mod = require2(candidate);
88278
- if (typeof mod.listConnectedProjects === "function" && typeof mod.connectedProjectsList === "function") {
88279
- return candidate;
88280
- }
88281
- } catch {}
88282
- }
88283
- return;
88284
- }
88285
- function normalizeRepositories(raw) {
88286
- const repositories = [];
88287
- if (!Array.isArray(raw.repositories))
88288
- return repositories;
88289
- for (const value of raw.repositories) {
88290
- const row = value;
88291
- if (!row || typeof row !== "object" || typeof row.worktree !== "string")
88292
- continue;
88293
- const type = row.type;
88294
- const direction = row.direction;
88295
- if (type !== "dependency" && type !== "implementation" && type !== "notes" && type !== "related" || direction !== "incoming" && direction !== "outgoing" || typeof row.depth !== "number" || typeof row.repository !== "string" || row.worktree.length === 0)
88296
- continue;
88297
- repositories.push({
88298
- depth: row.depth,
88299
- direction,
88300
- repository: row.repository,
88301
- type,
88302
- worktree: row.worktree
88303
- });
88304
- }
88305
- return repositories;
88306
- }
88307
- function discoverProjects(scoutModule, folder2) {
88308
- return new Promise((resolve16) => {
88309
- execFile2(process.execPath, ["-e", scoutScript, scoutModule, folder2], {
88310
- timeout: 60000,
88311
- maxBuffer: 8 * 1024 * 1024,
88312
- env: { ...process.env, NODE_OPTIONS: "" }
88313
- }, (error51, stdout) => {
88314
- if (error51) {
88315
- resolve16({
88316
- message: "Raggle Scout discovery timed out or failed. Retry the view to try again.",
88317
- projects: [],
88318
- status: "error"
88319
- });
88320
- return;
88321
- }
88322
- try {
88323
- const list = JSON.parse(stdout);
88324
- resolve16({
88325
- projects: normalizeRepositories(list),
88326
- status: "available"
88327
- });
88328
- } catch (parseError) {
88329
- resolve16({
88330
- message: `Could not parse the Scout response: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
88331
- projects: [],
88332
- status: "error"
88333
- });
88334
- }
88335
- });
88336
- });
88337
- }
88338
- var TTL = 60000;
88339
- var cache = new Map;
88340
- var stamps = new Map;
88341
- var inflight = new Map;
88342
- var queue = Promise.resolve();
88343
- function requestProjects(scoutModule, folder2) {
88344
- const existing = inflight.get(folder2);
88345
- if (existing)
88346
- return existing;
88347
- const run = queue.then(() => discoverProjects(scoutModule, folder2), () => discoverProjects(scoutModule, folder2));
88348
- queue = run.then(() => {
88349
- return;
88350
- }, () => {
88351
- return;
88352
- });
88353
- inflight.set(folder2, run);
88354
- return run.finally(() => {
88355
- inflight.delete(folder2);
88356
- });
88357
- }
88358
- async function loadProjects(context) {
88359
- const folder2 = context.folder;
88360
- const cached2 = cache.get(folder2);
88361
- const stamp = stamps.get(folder2) ?? 0;
88362
- if (cached2 && Date.now() - stamp < TTL)
88363
- return cached2;
88364
- const scoutModule = resolveScoutModule();
88365
- if (cached2 && !scoutModule)
88366
- return cached2;
88367
- if (scoutModule) {
88368
- const promise2 = requestProjects(scoutModule, folder2);
88369
- if (cached2) {
88370
- promise2.then((load2) => {
88371
- if (load2.status === "available") {
88372
- cache.set(folder2, load2);
88373
- stamps.set(folder2, Date.now());
88374
- }
88375
- });
88376
- return cached2;
88377
- }
88378
- const load = await promise2;
88379
- if (load.status === "available") {
88380
- cache.set(folder2, load);
88381
- stamps.set(folder2, Date.now());
88382
- }
88383
- return load;
88384
- }
88385
- return {
88386
- message: "Raggle Scout module (@raggle-ai/local with connected-project discovery) could not be resolved.",
88387
- projects: [],
88388
- status: "error"
88389
- };
88390
- }
88391
- function createProjectsPlugin() {
88392
- return {
88393
- ...projectsManifest,
88394
- async detect(_context) {
88395
- return { status: "available" };
88396
- },
88397
- async handle(context, _subpath) {
88398
- const load = await loadProjects(context);
88399
- if (load.status === "error") {
88400
- return {
88401
- kind: "available",
88402
- data: {
88403
- folder: context.folder,
88404
- message: load.message,
88405
- projects: [],
88406
- status: "error"
88407
- }
88408
- };
88409
- }
88410
- return {
88411
- kind: "available",
88412
- data: {
88413
- folder: context.folder,
88414
- projects: load.projects,
88415
- status: "available"
88416
- }
88417
- };
88418
- }
88419
- };
88420
- }
88421
-
88422
- // plugins/tasks/src/server.ts
88423
- import { readdir as readdir8, readFile as readFile13, stat as stat8 } from "node:fs/promises";
88424
- import { basename as basename8, dirname as dirname13, relative as relative12, resolve as resolve16, sep as sep10 } from "node:path";
88425
-
88426
- // packages/folder-plugin/src/subpath.ts
88427
- function matchSubpath(sources, subpath, itemsOf, matches) {
88428
- const id = subpath.trim();
88429
- if (!id)
88430
- return;
88431
- for (const source of sources) {
88432
- for (const item of itemsOf(source)) {
88433
- if (matches(item, id))
88434
- return { item, source };
88435
- }
88436
- }
88437
- return;
88438
- }
88439
-
88440
- // plugins/tasks/src/manifest.ts
88441
- var tasksManifest = {
88442
- id: "tasks",
88443
- label: "Tasks"
88444
- };
88445
-
88446
- // plugins/tasks/src/server.ts
88447
- async function loadTaskSources(viewRoot, scanRoot = viewRoot) {
88448
- const visit = async (current) => {
88449
- const entries = await readdir8(current, { withFileTypes: true }).catch(() => []);
88450
- const paths2 = [];
88451
- for (const entry of entries) {
88452
- if (entry.isFile() && entry.name === "todo.json") {
88453
- paths2.push(resolve16(current, entry.name));
88454
- continue;
88455
- }
88456
- if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules")
88457
- continue;
88458
- paths2.push(...await visit(resolve16(current, entry.name)));
88459
- }
88460
- return paths2;
88461
- };
88462
- const paths = (await visit(scanRoot)).sort((left, right) => relative12(scanRoot, left).localeCompare(relative12(scanRoot, right)));
88463
- const issues = [];
88464
- const warnings = [];
88465
- const sources = [];
88466
- await Promise.all(paths.map(async (path) => {
88467
- const sourceText = await readFile13(path, "utf8");
88468
- const file3 = relative12(viewRoot, path).split(sep10).join("/");
88469
- const parsed = parseTodoSource(sourceText);
88470
- if (!parsed.ok) {
88471
- issues.push({ file: file3, problems: parsed.issues });
88472
- return;
88473
- }
88474
- warnings.push({
88475
- file: file3,
88476
- problems: [
88477
- ...todoFileWarnings(parsed.document),
88478
- ...await missingNoteIssues(parsed.document, dirname13(path))
88479
- ]
88480
- });
88481
- sources.push({
88482
- document: parsed.document,
88483
- folder: dirname13(path),
88484
- path: file3
88485
- });
88486
- }));
88487
- issues.sort((left, right) => left.file.localeCompare(right.file));
88488
- return { issues, sources, warnings };
88489
- }
88490
- async function loadLinkedTaskSources(viewRoot, linkedFolders) {
88491
- const loads = await Promise.all(linkedFolders.map((folder2) => loadTaskSources(viewRoot, folder2)));
88492
- return {
88493
- issues: loads.flatMap((load) => load.issues),
88494
- sources: loads.flatMap((load) => load.sources),
88495
- warnings: loads.flatMap((load) => load.warnings)
88496
- };
88497
- }
88498
- function flattenIssues2(load) {
88499
- return load.issues.flatMap(({ file: file3, problems }) => problems.map((problem) => ({
88500
- path: file3 === "todo.json" ? problem.path : `${file3}: ${problem.path}`,
88501
- message: problem.message
88502
- })));
88503
- }
88504
- function flattenWarnings(load) {
88505
- return load.warnings.flatMap(({ file: file3, problems }) => problems.map((problem) => ({
88506
- path: file3 === "todo.json" ? problem.path : `${file3}: ${problem.path}`,
88507
- message: problem.message,
88508
- severity: problem.severity
88509
- })));
88510
- }
88511
- async function missingNoteIssues(document3, folder2) {
88512
- const missing = [];
88513
- for (const [index, task] of document3.tasks.entries()) {
88514
- const notes = task.notes;
88515
- if (!Array.isArray(notes))
88516
- continue;
88517
- for (const [noteIndex, note] of notes.entries()) {
88518
- if (typeof note !== "string" || /^https?:\/\//i.test(note))
88519
- continue;
88520
- const target = (note.split("#", 1)[0] ?? "").trim();
88521
- if (!target)
88522
- continue;
88523
- const exists = await stat8(resolve16(folder2, target)).then((entry) => entry.isFile()).catch(() => false);
88524
- if (!exists) {
88525
- missing.push({
88526
- message: `references a missing note file: ${target}`,
88527
- path: `tasks[${index}].notes[${noteIndex}]`,
88528
- severity: "warning"
88529
- });
88530
- }
88531
- }
88532
- }
88533
- return missing;
88534
- }
88535
- function taskCompleted(task) {
88536
- return task.done === true || task.status === "done";
88537
- }
88538
- function tasksOf(source) {
88539
- return Array.isArray(source.document.tasks) ? source.document.tasks : [];
88540
- }
88541
- function sourceTitle(folder2) {
88542
- return `${basename8(folder2)} tasks`;
88543
- }
88544
- function mergedDataset(folder2, sources) {
88545
- const root = sources.find((source) => source.path === "todo.json");
88546
- const title = root && typeof root.document.title === "string" ? root.document.title : sourceTitle(folder2);
88547
- const byFolder = new Map;
88548
- for (const source of sources) {
88549
- byFolder.set(source.folder, [
88550
- ...byFolder.get(source.folder) ?? [],
88551
- source
88552
- ]);
88553
- }
88554
- const ordered = new Map;
88555
- ordered.set(folder2, byFolder.get(folder2) ?? []);
88556
- for (const sourceFolder of [...byFolder.keys()].sort()) {
88557
- const docs = byFolder.get(sourceFolder);
88558
- if (docs && sourceFolder !== folder2)
88559
- ordered.set(sourceFolder, docs);
88560
- }
88561
- const datasetSources = [];
88562
- const tasks = [];
88563
- for (const [sourceFolder, docs] of ordered) {
88564
- const fileTasks = docs.flatMap(tasksOf);
88565
- const completed2 = fileTasks.filter(taskCompleted).length;
88566
- datasetSources.push({
88567
- id: sourceFolder,
88568
- open: fileTasks.length - completed2,
88569
- title: sourceTitle(sourceFolder),
88570
- total: fileTasks.length
88571
- });
88572
- for (const task of fileTasks) {
88573
- tasks.push({
88574
- ...task,
88575
- source: { id: sourceFolder, title: sourceTitle(sourceFolder) }
88576
- });
88577
- }
88578
- }
88579
- const completed = tasks.filter(taskCompleted).length;
88580
- return {
88581
- ...root?.document,
88582
- $schema: typeof root?.document.$schema === "string" ? root.document.$schema : "https://raggle.co/schemas/tasks.json",
88583
- id: typeof root?.document.id === "string" ? root.document.id : "todo",
88584
- project: { name: title },
88585
- sources: datasetSources,
88586
- summary: {
88587
- completed,
88588
- open: tasks.length - completed,
88589
- sources: datasetSources.length
88590
- },
88591
- tasks,
88592
- title,
88593
- version: typeof root?.document.version === "number" ? root.document.version : 1
88594
- };
88595
- }
88596
- function singleTaskDataset(folder2, source, task) {
88597
- const completed = taskCompleted(task);
88598
- return {
88599
- ...source.document,
88600
- $schema: typeof source.document.$schema === "string" ? source.document.$schema : "https://raggle.co/schemas/tasks.json",
88601
- version: typeof source.document.version === "number" ? source.document.version : 1,
88602
- project: { name: sourceTitle(folder2) },
88603
- sources: [
88604
- {
88605
- id: source.folder,
88606
- open: completed ? 0 : 1,
88607
- title: sourceTitle(source.folder),
88608
- total: 1
88609
- }
88610
- ],
88611
- summary: {
88612
- completed: completed ? 1 : 0,
88613
- open: completed ? 0 : 1,
88614
- sources: 1
88615
- },
88616
- tasks: [
88617
- {
88618
- ...task,
88619
- source: { id: source.folder, title: sourceTitle(source.folder) }
88620
- }
88621
- ]
88622
- };
88623
- }
88624
- function createTasksPlugin() {
88625
- return {
88626
- ...tasksManifest,
88627
- async detect(context) {
88628
- const [loads, linked] = await Promise.all([
88629
- loadTaskSources(context.folder),
88630
- loadLinkedTaskSources(context.folder, context.linkedFolders ?? [])
88631
- ]);
88632
- const merged = {
88633
- issues: [...loads.issues, ...linked.issues],
88634
- sources: [...loads.sources, ...linked.sources],
88635
- warnings: []
88636
- };
88637
- const noContent = merged.sources.length === 0 && merged.issues.length === 0;
88638
- if (noContent)
88639
- return { status: "unavailable" };
88640
- if (merged.issues.length > 0) {
88641
- return {
88642
- status: "error",
88643
- error: todoIssueSummary(flattenIssues2(merged))
88644
- };
88645
- }
88646
- return {
88647
- count: merged.sources.reduce((sum, source) => sum + tasksOf(source).length, 0),
88648
- status: "available"
88649
- };
88650
- },
88651
- async handle(context, subpath) {
88652
- const [load, linked] = await Promise.all([
88653
- loadTaskSources(context.folder),
88654
- loadLinkedTaskSources(context.folder, context.linkedFolders ?? [])
88655
- ]);
88656
- const sources = [...load.sources, ...linked.sources];
88657
- const rootIssue = load.issues.find((issue3) => issue3.file === "todo.json");
88658
- if (rootIssue) {
88659
- return {
88660
- kind: "available",
88661
- data: {
88662
- folder: context.folder,
88663
- issues: rootIssue.problems,
88664
- status: "error"
88665
- }
88666
- };
88667
- }
88668
- const warnings = flattenWarnings({
88669
- issues: [],
88670
- sources: [],
88671
- warnings: [...load.warnings, ...linked.warnings]
88672
- });
88673
- if (subpath === "" || sources.length === 0) {
88674
- return {
88675
- kind: "available",
88676
- data: {
88677
- dataset: mergedDataset(context.folder, sources),
88678
- folder: context.folder,
88679
- status: "available",
88680
- warnings
88681
- }
88682
- };
88683
- }
88684
- const id = subpath.trim();
88685
- const qualified = parseQualifiedTaskId(id);
88686
- if (qualified) {
88687
- const source = sources.find((item) => refMatchesFolder(qualified.ref, item.folder));
88688
- if (source) {
88689
- const task = tasksOf(source).find((item) => item.id === qualified.id || item.key === qualified.id);
88690
- return {
88691
- kind: "available",
88692
- data: task ? {
88693
- dataset: singleTaskDataset(context.folder, source, task),
88694
- folder: context.folder,
88695
- status: "available",
88696
- warnings
88697
- } : {
88698
- folder: context.folder,
88699
- message: `Task not found: ${id}`,
88700
- status: "error"
88701
- }
88702
- };
88703
- }
88704
- }
88705
- const bare = matchSubpath(sources, id, tasksOf, (item, target) => item.id === target || item.key === target);
88706
- if (bare) {
88707
- return {
88708
- kind: "available",
88709
- data: {
88710
- dataset: singleTaskDataset(context.folder, bare.source, bare.item),
88711
- folder: context.folder,
88712
- status: "available",
88713
- warnings
88714
- }
88715
- };
88716
- }
88717
- return {
88718
- kind: "available",
88719
- data: {
88720
- folder: context.folder,
88721
- message: `Task not found: ${id}`,
88722
- status: "error"
88723
- }
88724
- };
88725
- },
88726
- init(context) {
88727
- return {
88728
- files: [
88729
- { path: "todo.json", content: starterTodoSource(context.folder) }
88730
- ]
88731
- };
88732
- }
88733
- };
88734
- }
88735
85873
  // packages/api/src/server/plugins.ts
88736
85874
  function configuredFolderServerRegistry(registry2, resolvedConfig) {
88737
85875
  const disabled = new Set(resolvedConfig.plugins.disable);
@@ -88744,14 +85882,10 @@ function configuredFolderServerRegistry(registry2, resolvedConfig) {
88744
85882
  }
88745
85883
  function createFolderServerPlugins(options = {}) {
88746
85884
  return [
88747
- createCalendarPlugin(),
88748
- createMailPlugin(options),
88749
85885
  createAgentsPlugin({
88750
85886
  homeDir: options.agentsHomeDir
88751
85887
  }),
88752
- createNotesPlugin(),
88753
- createTasksPlugin(),
88754
- createProjectsPlugin()
85888
+ createNotesPlugin()
88755
85889
  ];
88756
85890
  }
88757
85891
  function folderServerRegistry(options = {}) {
@@ -88794,7 +85928,7 @@ async function folderServerRegistryAsync(options = {}) {
88794
85928
  ...server,
88795
85929
  async detect(context) {
88796
85930
  const detection = await server.detect(context);
88797
- if (detection.status === "unavailable" && existsSync4(resolve17(context.folder, entry.trigger))) {
85931
+ if (detection.status === "unavailable" && existsSync3(resolve11(context.folder, entry.trigger))) {
88798
85932
  return { status: "available" };
88799
85933
  }
88800
85934
  return detection;
@@ -88804,16 +85938,43 @@ async function folderServerRegistryAsync(options = {}) {
88804
85938
  }
88805
85939
  return registry2;
88806
85940
  }
85941
+ function externalApiEntries() {
85942
+ const source = process.env.KENNEL_EXTERNAL_API_ENTRIES;
85943
+ if (!source)
85944
+ return [];
85945
+ const value = JSON.parse(source);
85946
+ if (!Array.isArray(value))
85947
+ throw new Error("Invalid external plugin API config");
85948
+ return value.map((entry) => {
85949
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
85950
+ throw new Error("Invalid external plugin API config");
85951
+ const item = entry;
85952
+ if (typeof item.id !== "string" || typeof item.module !== "string" || typeof item.symbol !== "string")
85953
+ throw new Error("Invalid external plugin API config");
85954
+ return { id: item.id, module: item.module, symbol: item.symbol };
85955
+ });
85956
+ }
85957
+ async function externalApiHandlers() {
85958
+ const handlers = [];
85959
+ for (const entry of externalApiEntries()) {
85960
+ const imported = await import(pathToFileURL(entry.module).href);
85961
+ const handler = imported[entry.symbol];
85962
+ if (typeof handler !== "function")
85963
+ throw new Error(`External plugin ${entry.id} did not export API handler ${entry.symbol}`);
85964
+ handlers.push(handler);
85965
+ }
85966
+ return handlers;
85967
+ }
88807
85968
 
88808
85969
  // packages/api/src/server/project-discovery.ts
88809
- import { execFile as execFile4 } from "node:child_process";
85970
+ import { execFile as execFile3 } from "node:child_process";
88810
85971
 
88811
85972
  // packages/api/src/server/linked-folder-discovery.ts
88812
- import { execFile as execFile3 } from "node:child_process";
88813
- import { existsSync as existsSync5 } from "node:fs";
88814
- import { createRequire as createRequire3 } from "node:module";
88815
- import { dirname as dirname15, join as join5 } from "node:path";
88816
- import { fileURLToPath as fileURLToPath2 } from "node:url";
85973
+ import { execFile as execFile2 } from "node:child_process";
85974
+ import { existsSync as existsSync4 } from "node:fs";
85975
+ import { createRequire as createRequire2 } from "node:module";
85976
+ import { dirname as dirname9, join as join4 } from "node:path";
85977
+ import { fileURLToPath } from "node:url";
88817
85978
  var script = `
88818
85979
  const local = require(process.argv[1]);
88819
85980
  const { stat } = require('node:fs/promises');
@@ -88830,24 +85991,24 @@ const { stat } = require('node:fs/promises');
88830
85991
  })().catch(error => { console.error(error); process.exitCode = 1; });
88831
85992
  `;
88832
85993
  function resolveLocalModule(from = import.meta.url) {
88833
- const require2 = createRequire3(from);
85994
+ const require2 = createRequire2(from);
88834
85995
  const candidates = [];
88835
85996
  try {
88836
85997
  candidates.push(require2.resolve("@raggle-ai/local"));
88837
85998
  } catch {}
88838
- let directory = dirname15(fileURLToPath2(from));
88839
- while (dirname15(directory) !== directory) {
88840
- if (existsSync5(join5(directory, "raggle.json"))) {
85999
+ let directory = dirname9(fileURLToPath(from));
86000
+ while (dirname9(directory) !== directory) {
86001
+ if (existsSync4(join4(directory, "raggle.json"))) {
88841
86002
  for (const candidate of [
88842
- join5(dirname15(directory), "raggle-ai-scout", "packages", "local"),
88843
- join5(directory, "..", "..", "raggle-ai-scout", "packages", "local")
86003
+ join4(dirname9(directory), "raggle-ai-scout", "packages", "local"),
86004
+ join4(directory, "..", "..", "raggle-ai-scout", "packages", "local")
88844
86005
  ]) {
88845
86006
  if (!candidates.includes(candidate))
88846
86007
  candidates.push(candidate);
88847
86008
  }
88848
86009
  break;
88849
86010
  }
88850
- directory = dirname15(directory);
86011
+ directory = dirname9(directory);
88851
86012
  }
88852
86013
  for (const candidate of candidates) {
88853
86014
  try {
@@ -88856,11 +86017,11 @@ function resolveLocalModule(from = import.meta.url) {
88856
86017
  return candidate;
88857
86018
  } catch {}
88858
86019
  }
88859
- return candidates[0] ?? join5(dirname15(fileURLToPath2(from)), "..", "..", "..", "raggle-ai-scout", "packages", "local");
86020
+ return candidates[0] ?? join4(dirname9(fileURLToPath(from)), "..", "..", "..", "raggle-ai-scout", "packages", "local");
88860
86021
  }
88861
86022
  function discoverLinkedFolders(folder2, signal, modulePath = resolveLocalModule()) {
88862
- return new Promise((resolve18, reject) => {
88863
- execFile3(process.execPath, ["-e", script, modulePath, folder2], {
86023
+ return new Promise((resolve12, reject) => {
86024
+ execFile2(process.execPath, ["-e", script, modulePath, folder2], {
88864
86025
  signal,
88865
86026
  timeout: 60000,
88866
86027
  maxBuffer: 8 * 1024 * 1024,
@@ -88872,7 +86033,7 @@ function discoverLinkedFolders(folder2, signal, modulePath = resolveLocalModule(
88872
86033
  const folders = JSON.parse(stdout);
88873
86034
  if (!Array.isArray(folders) || !folders.every((path) => typeof path === "string"))
88874
86035
  throw new Error("Invalid linked folder discovery response");
88875
- resolve18(folders);
86036
+ resolve12(folders);
88876
86037
  } catch (error52) {
88877
86038
  reject(error52);
88878
86039
  }
@@ -88882,7 +86043,7 @@ function discoverLinkedFolders(folder2, signal, modulePath = resolveLocalModule(
88882
86043
  function createLinkedFolderDiscovery(load = discoverLinkedFolders, changed = () => {}) {
88883
86044
  const controller = new AbortController;
88884
86045
  const entries = new Map;
88885
- let queue2 = Promise.resolve();
86046
+ let queue = Promise.resolve();
88886
86047
  function read(folder2) {
88887
86048
  let entry = entries.get(folder2);
88888
86049
  if (controller.signal.aborted)
@@ -88895,7 +86056,7 @@ function createLinkedFolderDiscovery(load = discoverLinkedFolders, changed = ()
88895
86056
  }
88896
86057
  const current = entry;
88897
86058
  current.state = { folders: current.state.folders, status: "pending" };
88898
- current.pending = queue2.then(async () => {
86059
+ current.pending = queue.then(async () => {
88899
86060
  if (controller.signal.aborted)
88900
86061
  return;
88901
86062
  try {
@@ -88910,7 +86071,7 @@ function createLinkedFolderDiscovery(load = discoverLinkedFolders, changed = ()
88910
86071
  changed(folder2);
88911
86072
  }
88912
86073
  });
88913
- queue2 = current.pending;
86074
+ queue = current.pending;
88914
86075
  return current.state;
88915
86076
  }
88916
86077
  return {
@@ -88949,9 +86110,9 @@ local.discoverProjectsUnderFolder({folder}).then(projects => {
88949
86110
  name:p.name ?? basename(p.worktree), relativePath:relative(folder,p.worktree)}))));
88950
86111
  }).catch(error => { console.error(error); process.exitCode = 1; });
88951
86112
  `;
88952
- function discoverProjects2(folder2, signal) {
88953
- return new Promise((resolve18, reject) => {
88954
- execFile4(process.execPath, ["-e", script2, resolveLocalModule(), folder2], {
86113
+ function discoverProjects(folder2, signal) {
86114
+ return new Promise((resolve12, reject) => {
86115
+ execFile3(process.execPath, ["-e", script2, resolveLocalModule(), folder2], {
88955
86116
  signal,
88956
86117
  timeout: 60000,
88957
86118
  maxBuffer: 8 * 1024 * 1024,
@@ -88960,16 +86121,16 @@ function discoverProjects2(folder2, signal) {
88960
86121
  if (error51)
88961
86122
  return reject(error51);
88962
86123
  try {
88963
- resolve18(JSON.parse(stdout));
86124
+ resolve12(JSON.parse(stdout));
88964
86125
  } catch (error52) {
88965
86126
  reject(error52);
88966
86127
  }
88967
86128
  });
88968
86129
  });
88969
86130
  }
88970
- function createProjectDiscovery(load = discoverProjects2) {
86131
+ function createProjectDiscovery(load = discoverProjects) {
88971
86132
  const jobs = new Map;
88972
- let queue2 = Promise.resolve();
86133
+ let queue = Promise.resolve();
88973
86134
  let closed = false;
88974
86135
  return {
88975
86136
  read(folder2, signal) {
@@ -88978,21 +86139,21 @@ function createProjectDiscovery(load = discoverProjects2) {
88978
86139
  let job = jobs.get(folder2);
88979
86140
  if (!job) {
88980
86141
  const controller = new AbortController;
88981
- const promise2 = queue2.then(() => {
86142
+ const promise2 = queue.then(() => {
88982
86143
  controller.signal.throwIfAborted();
88983
86144
  return load(folder2, controller.signal);
88984
86145
  });
88985
86146
  job = { controller, promise: promise2, users: 0 };
88986
86147
  jobs.set(folder2, job);
88987
86148
  const current2 = job;
88988
- queue2 = promise2.then(() => {}, () => {}).finally(() => {
86149
+ queue = promise2.then(() => {}, () => {}).finally(() => {
88989
86150
  if (jobs.get(folder2) === current2)
88990
86151
  jobs.delete(folder2);
88991
86152
  });
88992
86153
  }
88993
86154
  const current = job;
88994
86155
  current.users++;
88995
- return new Promise((resolve18, reject) => {
86156
+ return new Promise((resolve12, reject) => {
88996
86157
  let done = false;
88997
86158
  const finish = (error51, value) => {
88998
86159
  if (done)
@@ -89007,7 +86168,7 @@ function createProjectDiscovery(load = discoverProjects2) {
89007
86168
  if (error51)
89008
86169
  reject(error51);
89009
86170
  else
89010
- resolve18(value);
86171
+ resolve12(value);
89011
86172
  };
89012
86173
  const abort = () => finish(new Error("Discovery cancelled"));
89013
86174
  signal.addEventListener("abort", abort, { once: true });
@@ -89200,13 +86361,13 @@ function skillDescription(content) {
89200
86361
  ---`, 4);
89201
86362
  if (end < 0)
89202
86363
  return;
89203
- const schema = import_yaml5.parse(content.slice(4, end));
86364
+ const schema = import_yaml4.parse(content.slice(4, end));
89204
86365
  return typeof schema.description === "string" ? schema.description : undefined;
89205
86366
  }
89206
86367
  function skillSource(folder2, root) {
89207
86368
  if (root === folder2)
89208
86369
  return "local";
89209
- if (root.startsWith(resolve18(folder2, ".gtl") + sep12))
86370
+ if (root.startsWith(resolve12(folder2, ".gtl") + sep7))
89210
86371
  return "local";
89211
86372
  return "global";
89212
86373
  }
@@ -89242,10 +86403,10 @@ var metadataExtensions = new Set([
89242
86403
  ".txt"
89243
86404
  ]);
89244
86405
  function metadataFile(path, root) {
89245
- const candidate = relative14(root, path);
89246
- if (!candidate || candidate.startsWith("..") || isAbsolute9(candidate))
86406
+ const candidate = relative10(root, path);
86407
+ if (!candidate || candidate.startsWith("..") || isAbsolute6(candidate))
89247
86408
  return false;
89248
- const parts = candidate.split(sep12);
86409
+ const parts = candidate.split(sep7);
89249
86410
  if (parts.some((part) => part === "node_modules"))
89250
86411
  return false;
89251
86412
  return metadataExtensions.has(extname6(candidate).toLowerCase());
@@ -89259,18 +86420,18 @@ function label(value) {
89259
86420
  return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (match) => match.toUpperCase());
89260
86421
  }
89261
86422
  function folderPath(value, root, lockedRoot) {
89262
- const folder2 = realpathSync3(resolve18(value ?? root));
86423
+ const folder2 = realpathSync2(resolve12(value ?? root));
89263
86424
  if (!lockedRoot)
89264
86425
  return folder2;
89265
- const path = relative14(lockedRoot, folder2);
89266
- if (!path || !path.startsWith("..") && !isAbsolute9(path))
86426
+ const path = relative10(lockedRoot, folder2);
86427
+ if (!path || !path.startsWith("..") && !isAbsolute6(path))
89267
86428
  return folder2;
89268
86429
  throw new Error(`Folder is outside the viewer root: ${folder2}`);
89269
86430
  }
89270
86431
  function openPathHref(path) {
89271
- const url3 = new URL("/api/open-path", "http://127.0.0.1");
89272
- url3.searchParams.set("path", path);
89273
- return `${url3.pathname}${url3.search}`;
86432
+ const url2 = new URL("/api/open-path", "http://127.0.0.1");
86433
+ url2.searchParams.set("path", path);
86434
+ return `${url2.pathname}${url2.search}`;
89274
86435
  }
89275
86436
  var loopbackServerHosts = new Set([
89276
86437
  "127.0.0.1",
@@ -89319,12 +86480,12 @@ async function searchIconDataUrl(icon2) {
89319
86480
  const mediaType = searchIconTypes.get(extname6(icon2).toLowerCase());
89320
86481
  if (!mediaType)
89321
86482
  return;
89322
- const details = await stat9(icon2).catch(() => {
86483
+ const details = await stat8(icon2).catch(() => {
89323
86484
  return;
89324
86485
  });
89325
86486
  if (!details?.size || details.size > maxIconBytes3)
89326
86487
  return;
89327
- return `data:${mediaType};base64,${(await readFile14(icon2)).toString("base64")}`;
86488
+ return `data:${mediaType};base64,${(await readFile9(icon2)).toString("base64")}`;
89328
86489
  }
89329
86490
  async function iconWithFaviconFallback(folder2, assets2, assetFiles) {
89330
86491
  return folderIcon(folder2, assets2, assetFiles, async (folderName) => {
@@ -89362,7 +86523,7 @@ function unique2(values) {
89362
86523
  return [...new Set(values)];
89363
86524
  }
89364
86525
  async function existingDirectory(path) {
89365
- const details = await stat9(path).catch(() => {
86526
+ const details = await stat8(path).catch(() => {
89366
86527
  return;
89367
86528
  });
89368
86529
  return details?.isDirectory() ? path : undefined;
@@ -89372,7 +86533,7 @@ function metadataPaths(metadata) {
89372
86533
  }
89373
86534
  function ancestorFolders(folder2) {
89374
86535
  const folders = [];
89375
- for (let current = folder2;dirname16(current) !== current; current = dirname16(current)) {
86536
+ for (let current = folder2;dirname10(current) !== current; current = dirname10(current)) {
89376
86537
  folders.push(current);
89377
86538
  }
89378
86539
  return folders;
@@ -89380,13 +86541,13 @@ function ancestorFolders(folder2) {
89380
86541
  async function linkedSkillRoots(folder2, metadata) {
89381
86542
  const gtlNames = metadataPaths(metadata).map((path) => path.split(/[\\/]\.gtl[\\/]/)[1]).filter((path) => Boolean(path)).map((path) => path.split(/[\\/]/)[0]).filter((name2) => Boolean(name2));
89382
86543
  const gtlRoots = gtlNames.flatMap((name2) => [
89383
- resolve18(folder2, ".gtl", name2),
89384
- ...ancestorFolders(folder2).map((ancestor) => resolve18(ancestor, name2))
86544
+ resolve12(folder2, ".gtl", name2),
86545
+ ...ancestorFolders(folder2).map((ancestor) => resolve12(ancestor, name2))
89385
86546
  ]);
89386
86547
  const name = folder2.replace(/[\\/]+$/, "").split(/[\\/]/).at(-1);
89387
86548
  const siblingIndex = name ? [
89388
- resolve18(dirname16(folder2), `${name}-index`),
89389
- resolve18(dirname16(dirname16(folder2)), `${name}-index`)
86549
+ resolve12(dirname10(folder2), `${name}-index`),
86550
+ resolve12(dirname10(dirname10(folder2)), `${name}-index`)
89390
86551
  ] : [];
89391
86552
  const roots = await Promise.all(unique2([...gtlRoots, ...siblingIndex]).map(existingDirectory));
89392
86553
  return roots.filter((path) => Boolean(path));
@@ -89394,24 +86555,24 @@ async function linkedSkillRoots(folder2, metadata) {
89394
86555
  async function listFolderSkills(folder2, metadata) {
89395
86556
  const roots = unique2([folder2, ...await linkedSkillRoots(folder2, metadata)]);
89396
86557
  const skills = await Promise.all(roots.map(async (root) => {
89397
- const skillsFolder = resolve18(root, ".agents", "skills");
89398
- const entries = await readdir9(skillsFolder, {
86558
+ const skillsFolder = resolve12(root, ".agents", "skills");
86559
+ const entries = await readdir5(skillsFolder, {
89399
86560
  withFileTypes: true
89400
86561
  }).catch(() => []);
89401
86562
  return (await Promise.all(entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map(async (entry) => {
89402
- const path = resolve18(skillsFolder, entry.name);
89403
- const skillPath = resolve18(path, "SKILL.md");
89404
- const details = await stat9(skillPath).catch(() => {
86563
+ const path = resolve12(skillsFolder, entry.name);
86564
+ const skillPath = resolve12(path, "SKILL.md");
86565
+ const details = await stat8(skillPath).catch(() => {
89405
86566
  return;
89406
86567
  });
89407
86568
  if (!details?.isFile())
89408
86569
  return;
89409
- const content = await readFile14(skillPath, "utf8").catch(() => "");
86570
+ const content = await readFile9(skillPath, "utf8").catch(() => "");
89410
86571
  return {
89411
86572
  description: skillDescription(content),
89412
86573
  name: label(entry.name),
89413
86574
  openHref: openPathHref(path),
89414
- relativePath: relative14(root, path),
86575
+ relativePath: relative10(root, path),
89415
86576
  source: skillSource(folder2, root)
89416
86577
  };
89417
86578
  }))).filter((skill2) => skill2);
@@ -89419,15 +86580,15 @@ async function listFolderSkills(folder2, metadata) {
89419
86580
  return unique2(skills.flat().map((skill2) => JSON.stringify(skill2))).map((skill2) => JSON.parse(skill2)).sort((left, right) => left.name.localeCompare(right.name));
89420
86581
  }
89421
86582
  function isMachineRoot(folder2) {
89422
- return folder2 === homedir6() || dirname16(folder2) === folder2;
86583
+ return folder2 === homedir4() || dirname10(folder2) === folder2;
89423
86584
  }
89424
86585
  async function shallowMetadata(folder2) {
89425
- const entries = await readdir9(folder2, { withFileTypes: true });
86586
+ const entries = await readdir5(folder2, { withFileTypes: true });
89426
86587
  const visible = entries.filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
89427
86588
  const files = await Promise.all(visible.filter((entry) => entry.isFile() && [".markdown", ".md"].includes(extname6(entry.name).toLowerCase())).map(async (entry) => {
89428
- const path = resolve18(folder2, entry.name);
89429
- const details = await stat9(path);
89430
- const content = await readFile14(path, "utf8").catch(() => "");
86589
+ const path = resolve12(folder2, entry.name);
86590
+ const details = await stat8(path);
86591
+ const content = await readFile9(path, "utf8").catch(() => "");
89431
86592
  const title = markdownMetadata(content).title;
89432
86593
  return {
89433
86594
  activityAt: details.mtimeMs,
@@ -89440,10 +86601,10 @@ async function shallowMetadata(folder2) {
89440
86601
  };
89441
86602
  }));
89442
86603
  const projects = visible.filter((entry) => entry.isDirectory()).map((entry) => ({
89443
- id: createHash7("sha1").update(resolve18(folder2, entry.name)).digest("hex"),
86604
+ id: createHash5("sha1").update(resolve12(folder2, entry.name)).digest("hex"),
89444
86605
  name: entry.name,
89445
86606
  relativePath: entry.name,
89446
- worktree: resolve18(folder2, entry.name)
86607
+ worktree: resolve12(folder2, entry.name)
89447
86608
  })).sort((left, right) => left.name.localeCompare(right.name));
89448
86609
  return {
89449
86610
  changes: { items: [] },
@@ -89470,8 +86631,8 @@ async function loadFolderData(folder2) {
89470
86631
  items: projects.map((project2) => ({
89471
86632
  id: project2.id,
89472
86633
  worktree: project2.worktree,
89473
- name: project2.name ?? basename9(project2.worktree),
89474
- relativePath: relative14(folder2, project2.worktree)
86634
+ name: project2.name ?? basename7(project2.worktree),
86635
+ relativePath: relative10(folder2, project2.worktree)
89475
86636
  }))
89476
86637
  }
89477
86638
  };
@@ -89479,10 +86640,10 @@ async function loadFolderData(folder2) {
89479
86640
  async function notesFiles(folder2, files, origin) {
89480
86641
  return {
89481
86642
  items: await Promise.all(files.map(async (file3) => {
89482
- const path = file3.path ?? (file3.relativePath ? resolve18(folder2, file3.relativePath) : undefined);
86643
+ const path = file3.path ?? (file3.relativePath ? resolve12(folder2, file3.relativePath) : undefined);
89483
86644
  if (!path)
89484
86645
  return origin ? { ...file3, folder: origin } : file3;
89485
- const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile14(path, "utf8").catch(() => "") : "";
86646
+ const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile9(path, "utf8").catch(() => "") : "";
89486
86647
  const title = content ? markdownMetadata(content).title : undefined;
89487
86648
  return {
89488
86649
  ...file3,
@@ -89493,9 +86654,9 @@ async function notesFiles(folder2, files, origin) {
89493
86654
  }))
89494
86655
  };
89495
86656
  }
89496
- async function localProjectSearchCache(cache2) {
89497
- if (cache2 && Date.now() - cache2.loadedAt < projectSearchCacheMs)
89498
- return cache2;
86657
+ async function localProjectSearchCache(cache) {
86658
+ if (cache && Date.now() - cache.loadedAt < projectSearchCacheMs)
86659
+ return cache;
89499
86660
  const settings = readRaggleUserSettings();
89500
86661
  const projects = applyProjectFavorites((await buildProjectCatalog({
89501
86662
  cloneSources: settings.cloneDirectories.map((cloneDirectory) => ({
@@ -89515,16 +86676,16 @@ async function localProjectSearchCache(cache2) {
89515
86676
  function mediaTarget(folder2, file3, lockedTarget) {
89516
86677
  if (!file3)
89517
86678
  return;
89518
- const rootReal = realpathSync3(resolve18(folder2));
89519
- const path = resolve18(folder2, file3);
86679
+ const rootReal = realpathSync2(resolve12(folder2));
86680
+ const path = resolve12(folder2, file3);
89520
86681
  let pathReal;
89521
86682
  try {
89522
- pathReal = realpathSync3(path);
86683
+ pathReal = realpathSync2(path);
89523
86684
  } catch {
89524
86685
  return;
89525
86686
  }
89526
- const rel = relative14(rootReal, pathReal);
89527
- if (rel.startsWith("..") || isAbsolute9(rel))
86687
+ const rel = relative10(rootReal, pathReal);
86688
+ if (rel.startsWith("..") || isAbsolute6(rel))
89528
86689
  return;
89529
86690
  if (lockedTarget && pathReal !== lockedTarget)
89530
86691
  return;
@@ -89563,16 +86724,16 @@ function documentError(response) {
89563
86724
  function documentTarget(folder2, file3, lockedTarget) {
89564
86725
  if (!file3)
89565
86726
  return;
89566
- const rootReal = realpathSync3(resolve18(folder2));
89567
- const path = resolve18(folder2, file3);
86727
+ const rootReal = realpathSync2(resolve12(folder2));
86728
+ const path = resolve12(folder2, file3);
89568
86729
  let pathReal;
89569
86730
  try {
89570
- pathReal = realpathSync3(path);
86731
+ pathReal = realpathSync2(path);
89571
86732
  } catch {
89572
86733
  return;
89573
86734
  }
89574
- const rel = relative14(rootReal, pathReal);
89575
- if (rel.startsWith("..") || isAbsolute9(rel))
86735
+ const rel = relative10(rootReal, pathReal);
86736
+ if (rel.startsWith("..") || isAbsolute6(rel))
89576
86737
  return;
89577
86738
  if (lockedTarget && pathReal !== lockedTarget)
89578
86739
  return;
@@ -89582,7 +86743,7 @@ function documentTarget(folder2, file3, lockedTarget) {
89582
86743
  return { kind, path: pathReal, root: rootReal };
89583
86744
  }
89584
86745
  function documentEtag(details) {
89585
- return `"${createHash7("sha256").update(`${details.mtimeMs}:${details.size}`).digest("hex")}"`;
86746
+ return `"${createHash5("sha256").update(`${details.mtimeMs}:${details.size}`).digest("hex")}"`;
89586
86747
  }
89587
86748
  async function projectSearchItem(project2, match) {
89588
86749
  const icon2 = await searchIconDataUrl(project2.icon) ?? await searchIconDataUrl(discoverProjectIcon(project2.worktree));
@@ -89617,14 +86778,14 @@ function parsePluginRegistryConfig(content) {
89617
86778
  };
89618
86779
  }
89619
86780
  async function nearestKennelConfig(folder2) {
89620
- for (let current = folder2;; current = dirname16(current)) {
89621
- const candidate = resolve18(current, "kennel.json");
89622
- const details = await stat9(candidate).catch(() => {
86781
+ for (let current = folder2;; current = dirname10(current)) {
86782
+ const candidate = resolve12(current, "kennel.json");
86783
+ const details = await stat8(candidate).catch(() => {
89623
86784
  return;
89624
86785
  });
89625
86786
  if (details?.isFile())
89626
86787
  return candidate;
89627
- if (dirname16(current) === current)
86788
+ if (dirname10(current) === current)
89628
86789
  return;
89629
86790
  }
89630
86791
  }
@@ -89633,14 +86794,14 @@ async function folderPluginRegistryConfig(folder2) {
89633
86794
  if (!path)
89634
86795
  return defaultPluginRegistryConfig;
89635
86796
  try {
89636
- return parsePluginRegistryConfig(await readFile14(path, "utf8"));
86797
+ return parsePluginRegistryConfig(await readFile9(path, "utf8"));
89637
86798
  } catch {
89638
86799
  return defaultPluginRegistryConfig;
89639
86800
  }
89640
86801
  }
89641
86802
  function pluginConfigWarnings(registry2, config2) {
89642
86803
  const ids = unique2([...config2.plugins.disable, ...config2.plugins.order]);
89643
- return ids.filter((id) => !registry2.has(id)).map((id) => `unknown plugin id "${id}" — not bundled in this build`);
86804
+ return ids.filter((id) => !registry2.has(id)).map((id) => `unknown plugin id "${id}" — not installed in this build`);
89644
86805
  }
89645
86806
  async function pluginAvailability(registry2, folder2, linkedFolders, configWarnings = []) {
89646
86807
  const items2 = [];
@@ -89672,25 +86833,21 @@ function createFolderApi({
89672
86833
  restrictToRoot = false,
89673
86834
  root,
89674
86835
  sessionFinding,
89675
- mailPreviewer,
89676
- mailDir,
89677
- mailos,
89678
86836
  agentsHomeDir,
89679
86837
  linkedFolderLoader,
89680
86838
  remoteAccess = false,
89681
86839
  lockedFile
89682
86840
  }) {
89683
- let lockedRoot = restrictToRoot ? realpathSync3(root) : undefined;
89684
- const lockedTarget = lockedFile ? realpathSync3(lockedFile) : undefined;
86841
+ let lockedRoot = restrictToRoot ? realpathSync2(root) : undefined;
86842
+ const lockedTarget = lockedFile ? realpathSync2(lockedFile) : undefined;
89685
86843
  const resolveSession = sessionFinding ?? {
89686
86844
  listFolderSessions,
89687
86845
  readSessionTranscript
89688
86846
  };
89689
86847
  const basePlugins = folderServerRegistryAsync({
89690
- mailDir,
89691
- previewer: mailPreviewer,
89692
86848
  agentsHomeDir
89693
86849
  });
86850
+ const baseExternalApiHandlers = externalApiHandlers();
89694
86851
  const assetFiles = new Map;
89695
86852
  let projectSearchCache;
89696
86853
  const projectDiscovery = createProjectDiscovery();
@@ -89714,7 +86871,7 @@ function createFolderApi({
89714
86871
  const serverHost = server.host;
89715
86872
  const remote = remoteAccess || serverHost === true || typeof serverHost === "string" && !isLoopbackServerHost(serverHost);
89716
86873
  if (remote)
89717
- lockedRoot = lockedRoot ?? realpathSync3(root);
86874
+ lockedRoot = lockedRoot ?? realpathSync2(root);
89718
86875
  const collectionServer = createSchemaCollectionServer(server, {
89719
86876
  boundary: lockedRoot,
89720
86877
  lockedTarget
@@ -89740,7 +86897,7 @@ function createFolderApi({
89740
86897
  if (!folder2)
89741
86898
  return;
89742
86899
  server.emit({
89743
- data: { path: relative14(folder2, path) },
86900
+ data: { path: relative10(folder2, path) },
89744
86901
  event: "kennel:metadata-changed",
89745
86902
  type: "custom"
89746
86903
  });
@@ -89749,7 +86906,7 @@ function createFolderApi({
89749
86906
  watch2(root);
89750
86907
  server.onChange(refresh);
89751
86908
  server.use(async (request, response, next) => {
89752
- const url3 = new URL(request.url ?? "/", "http://127.0.0.1");
86909
+ const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
89753
86910
  if (!apiHostAllowed(request.headers.host, remote)) {
89754
86911
  response.writeHead(403, {
89755
86912
  "Content-Type": "text/plain; charset=utf-8"
@@ -89757,10 +86914,10 @@ function createFolderApi({
89757
86914
  response.end("Untrusted Host header");
89758
86915
  return;
89759
86916
  }
89760
- if (!validateQuery(request, response, url3))
86917
+ if (!validateQuery(request, response, url2))
89761
86918
  return;
89762
- if (url3.pathname === "/api/capabilities") {
89763
- sendContract(response, url3, {
86919
+ if (url2.pathname === "/api/capabilities") {
86920
+ sendContract(response, url2, {
89764
86921
  acp: !remote,
89765
86922
  download: true,
89766
86923
  hostActions: remote ? "none" : "local",
@@ -89769,22 +86926,22 @@ function createFolderApi({
89769
86926
  }, { "Cache-Control": "no-store" });
89770
86927
  return;
89771
86928
  }
89772
- if (url3.pathname === "/api/config") {
89773
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
89774
- sendContract(response, url3, { aiInputModule: await configuredAiInputModule(folder2) }, { "Cache-Control": "no-store" });
86929
+ if (url2.pathname === "/api/config") {
86930
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
86931
+ sendContract(response, url2, { aiInputModule: await configuredAiInputModule(folder2) }, { "Cache-Control": "no-store" });
89775
86932
  return;
89776
86933
  }
89777
- if (url3.pathname === "/api/health") {
89778
- sendContract(response, url3, { viewer: viewerName, root, kernelKey: process.env.KENNEL_KERNEL_KEY ?? "core" }, { "Cache-Control": "no-store" });
86934
+ if (url2.pathname === "/api/health") {
86935
+ sendContract(response, url2, { viewer: viewerName, root, kernelKey: process.env.KENNEL_KERNEL_KEY ?? "core" }, { "Cache-Control": "no-store" });
89779
86936
  return;
89780
86937
  }
89781
- if (url3.pathname === "/api/folder-favicon") {
86938
+ if (url2.pathname === "/api/folder-favicon") {
89782
86939
  const data = await (async () => {
89783
86940
  if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
89784
86941
  return;
89785
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
86942
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
89786
86943
  const host = await folderWebsite(folder2);
89787
- if (!host || host !== url3.searchParams.get("host"))
86944
+ if (!host || host !== url2.searchParams.get("host"))
89788
86945
  return;
89789
86946
  return favicon(host);
89790
86947
  })().catch(() => {
@@ -89798,7 +86955,7 @@ function createFolderApi({
89798
86955
  response.end(data ?? "Icon unavailable");
89799
86956
  return;
89800
86957
  }
89801
- const assetMatch = /^\/api\/folders\/([^/]+)\/assets\/([a-f0-9]{64})\.(?:jpg|png)$/.exec(url3.pathname);
86958
+ const assetMatch = /^\/api\/folders\/([^/]+)\/assets\/([a-f0-9]{64})\.(?:jpg|png)$/.exec(url2.pathname);
89802
86959
  if (assetMatch) {
89803
86960
  try {
89804
86961
  const [, folderId, hash3] = assetMatch;
@@ -89824,7 +86981,7 @@ function createFolderApi({
89824
86981
  "Content-Type": asset.mediaType,
89825
86982
  ETag: `"${hash3}"`
89826
86983
  });
89827
- response.end(await readFile14(asset.path));
86984
+ response.end(await readFile9(asset.path));
89828
86985
  } catch (error51) {
89829
86986
  response.writeHead(404, {
89830
86987
  "Content-Type": "text/plain; charset=utf-8"
@@ -89833,19 +86990,19 @@ function createFolderApi({
89833
86990
  }
89834
86991
  return;
89835
86992
  }
89836
- if (remote && ["/api/project-search", "/api/entities"].includes(url3.pathname)) {
86993
+ if (remote && ["/api/project-search", "/api/entities"].includes(url2.pathname)) {
89837
86994
  response.writeHead(403);
89838
86995
  response.end("Global discovery is disabled for remote access");
89839
86996
  return;
89840
86997
  }
89841
- if (url3.pathname === "/api/project-search") {
89842
- const query = url3.searchParams.get("q")?.trim() ?? "";
89843
- const cache2 = await localProjectSearchCache(projectSearchCache);
89844
- projectSearchCache = cache2;
89845
- const result = searchProjectIndex(cache2.index, query, {
86998
+ if (url2.pathname === "/api/project-search") {
86999
+ const query = url2.searchParams.get("q")?.trim() ?? "";
87000
+ const cache = await localProjectSearchCache(projectSearchCache);
87001
+ projectSearchCache = cache;
87002
+ const result = searchProjectIndex(cache.index, query, {
89846
87003
  limit: query ? 40 : 20
89847
87004
  });
89848
- sendContract(response, url3, {
87005
+ sendContract(response, url2, {
89849
87006
  items: await Promise.all(result.hits.map((hit) => projectSearchItem(hit.project, hit))),
89850
87007
  query,
89851
87008
  scanned: result.scanned,
@@ -89853,10 +87010,10 @@ function createFolderApi({
89853
87010
  }, { "Cache-Control": "no-store" });
89854
87011
  return;
89855
87012
  }
89856
- if (url3.pathname === "/api/entities") {
87013
+ if (url2.pathname === "/api/entities") {
89857
87014
  try {
89858
- const workspace2 = await loadEntityWorkspace(url3.searchParams.get("root") ?? undefined, url3.searchParams.get("q") ?? "", url3.searchParams.get("entity"), root);
89859
- sendContract(response, url3, workspace2, {
87015
+ const workspace2 = await loadEntityWorkspace(url2.searchParams.get("root") ?? undefined, url2.searchParams.get("q") ?? "", url2.searchParams.get("entity"), root);
87016
+ sendContract(response, url2, workspace2, {
89860
87017
  "Cache-Control": "no-store"
89861
87018
  });
89862
87019
  } catch (error51) {
@@ -89867,7 +87024,7 @@ function createFolderApi({
89867
87024
  }
89868
87025
  return;
89869
87026
  }
89870
- if (url3.pathname === "/api/document/edit") {
87027
+ if (url2.pathname === "/api/document/edit") {
89871
87028
  if (remote) {
89872
87029
  response.writeHead(403, {
89873
87030
  "Content-Type": "text/plain; charset=utf-8"
@@ -89875,51 +87032,51 @@ function createFolderApi({
89875
87032
  response.end("Host actions are disabled for remote access");
89876
87033
  return;
89877
87034
  }
89878
- await runApiRoute(request, response, url3, documentEditRoute, {
87035
+ await runApiRoute(request, response, url2, documentEditRoute, {
89879
87036
  lockedFile: lockedTarget,
89880
87037
  authorize: (folder2) => folderPath(folder2, root, lockedRoot)
89881
87038
  });
89882
87039
  return;
89883
87040
  }
89884
- if (url3.pathname === "/api/open-folder") {
87041
+ if (url2.pathname === "/api/open-folder") {
89885
87042
  if (remote || request.method !== "POST" || !sameOrigin(request)) {
89886
87043
  response.writeHead(remote || request.method === "POST" ? 403 : 405);
89887
87044
  response.end("Folder action not allowed");
89888
87045
  return;
89889
87046
  }
89890
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
87047
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
89891
87048
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
89892
- await new Promise((done, fail) => execFile5(command, [folder2], (error51) => error51 ? fail(error51) : done()));
87049
+ await new Promise((done, fail) => execFile4(command, [folder2], (error51) => error51 ? fail(error51) : done()));
89893
87050
  response.writeHead(204);
89894
87051
  response.end();
89895
87052
  return;
89896
87053
  }
89897
- if (url3.pathname === "/api/documents") {
89898
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
89899
- const target = lockedTarget ? documentTarget(folder2, relative14(folder2, lockedTarget), lockedTarget) : undefined;
87054
+ if (url2.pathname === "/api/documents") {
87055
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
87056
+ const target = lockedTarget ? documentTarget(folder2, relative10(folder2, lockedTarget), lockedTarget) : undefined;
89900
87057
  const dataset = lockedTarget ? {
89901
87058
  root: folder2,
89902
87059
  documents: target ? [
89903
87060
  {
89904
- path: relative14(folder2, target.path),
87061
+ path: relative10(folder2, target.path),
89905
87062
  kind: target.kind,
89906
- content: previewTextKind(target.kind) ? await readFile14(target.path, "utf8") : ""
87063
+ content: previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : ""
89907
87064
  }
89908
87065
  ] : []
89909
87066
  } : await readFolder(folder2);
89910
87067
  watch2(folder2);
89911
- sendContract(response, url3, dataset, { "Cache-Control": "no-store" });
87068
+ sendContract(response, url2, dataset, { "Cache-Control": "no-store" });
89912
87069
  return;
89913
87070
  }
89914
- if (url3.pathname === "/api/document") {
87071
+ if (url2.pathname === "/api/document") {
89915
87072
  try {
89916
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
89917
- const target = documentTarget(folder2, url3.searchParams.get("file"), lockedTarget);
87073
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
87074
+ const target = documentTarget(folder2, url2.searchParams.get("file"), lockedTarget);
89918
87075
  if (!target) {
89919
87076
  documentError(response);
89920
87077
  return;
89921
87078
  }
89922
- const details = await stat9(target.path);
87079
+ const details = await stat8(target.path);
89923
87080
  if (!details.isFile()) {
89924
87081
  documentError(response);
89925
87082
  return;
@@ -89931,16 +87088,16 @@ function createFolderApi({
89931
87088
  return;
89932
87089
  }
89933
87090
  watch2(folder2);
89934
- const content = previewTextKind(target.kind) ? await readFile14(target.path, "utf8") : "";
89935
- sendContract(response, url3, {
87091
+ const content = previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : "";
87092
+ sendContract(response, url2, {
89936
87093
  content,
89937
87094
  kind: target.kind,
89938
- path: relative14(target.root, target.path),
87095
+ path: relative10(target.root, target.path),
89939
87096
  root: folder2,
89940
87097
  ...previewTextKind(target.kind) ? {} : {
89941
87098
  rawUrl: `/api/raw?${new URLSearchParams({
89942
87099
  path: folder2,
89943
- file: relative14(target.root, target.path)
87100
+ file: relative10(target.root, target.path)
89944
87101
  })}`
89945
87102
  }
89946
87103
  }, {
@@ -89952,15 +87109,15 @@ function createFolderApi({
89952
87109
  }
89953
87110
  return;
89954
87111
  }
89955
- if (url3.pathname === "/api/raw") {
87112
+ if (url2.pathname === "/api/raw") {
89956
87113
  try {
89957
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
89958
- const target = documentTarget(folder2, url3.searchParams.get("file"), lockedTarget);
87114
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
87115
+ const target = documentTarget(folder2, url2.searchParams.get("file"), lockedTarget);
89959
87116
  if (!target) {
89960
87117
  documentError(response);
89961
87118
  return;
89962
87119
  }
89963
- const details = await stat9(target.path);
87120
+ const details = await stat8(target.path);
89964
87121
  if (!details.isFile()) {
89965
87122
  documentError(response);
89966
87123
  return;
@@ -89973,7 +87130,7 @@ function createFolderApi({
89973
87130
  }
89974
87131
  watch2(folder2);
89975
87132
  const active = target.kind === "html" || target.path.toLowerCase().endsWith(".svg");
89976
- const download = url3.searchParams.get("download") === "1" || active;
87133
+ const download = url2.searchParams.get("download") === "1" || active;
89977
87134
  const mime = target.kind === "html" ? "application/octet-stream" : previewRawMime(target.path) ?? "application/octet-stream";
89978
87135
  response.writeHead(200, {
89979
87136
  "Cache-Control": "private, max-age=0, must-revalidate",
@@ -89981,24 +87138,24 @@ function createFolderApi({
89981
87138
  ETag: etag,
89982
87139
  "X-Content-Type-Options": "nosniff",
89983
87140
  ...download ? {
89984
- "Content-Disposition": `attachment; filename="${basename9(target.path)}"`
87141
+ "Content-Disposition": `attachment; filename="${basename7(target.path)}"`
89985
87142
  } : {}
89986
87143
  });
89987
- response.end(await readFile14(target.path));
87144
+ response.end(await readFile9(target.path));
89988
87145
  } catch (error51) {
89989
87146
  documentError(response);
89990
87147
  }
89991
87148
  return;
89992
87149
  }
89993
- if (url3.pathname === "/api/media") {
87150
+ if (url2.pathname === "/api/media") {
89994
87151
  try {
89995
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
89996
- const target = mediaTarget(folder2, url3.searchParams.get("file"), lockedTarget);
87152
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
87153
+ const target = mediaTarget(folder2, url2.searchParams.get("file"), lockedTarget);
89997
87154
  if (!target) {
89998
87155
  documentError(response);
89999
87156
  return;
90000
87157
  }
90001
- const details = await stat9(target.path);
87158
+ const details = await stat8(target.path);
90002
87159
  if (!details.isFile()) {
90003
87160
  documentError(response);
90004
87161
  return;
@@ -90010,7 +87167,7 @@ function createFolderApi({
90010
87167
  return;
90011
87168
  }
90012
87169
  watch2(folder2);
90013
- const data = await readFile14(target.path);
87170
+ const data = await readFile9(target.path);
90014
87171
  const baseHeaders = {
90015
87172
  "Accept-Ranges": "bytes",
90016
87173
  "Cache-Control": "private, max-age=0, must-revalidate",
@@ -90047,9 +87204,9 @@ function createFolderApi({
90047
87204
  }
90048
87205
  return;
90049
87206
  }
90050
- if (url3.pathname === "/api/session") {
87207
+ if (url2.pathname === "/api/session") {
90051
87208
  try {
90052
- const sessionId = url3.searchParams.get("sessionId");
87209
+ const sessionId = url2.searchParams.get("sessionId");
90053
87210
  if (!sessionId)
90054
87211
  throw new Error("No sessionId parameter");
90055
87212
  const session2 = await resolveSession.readSessionTranscript(sessionId);
@@ -90066,20 +87223,20 @@ function createFolderApi({
90066
87223
  }
90067
87224
  return;
90068
87225
  }
90069
- const pluginMatch = /^\/api\/plugins\/([^/]+)$/.exec(url3.pathname);
87226
+ const pluginMatch = /^\/api\/plugins\/([^/]+)$/.exec(url2.pathname);
90070
87227
  if (pluginMatch) {
90071
87228
  try {
90072
87229
  const pluginId = pluginMatch[1];
90073
87230
  if (!pluginId)
90074
87231
  throw new Error("Unknown plugin");
90075
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
87232
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
90076
87233
  const pluginConfig = await folderPluginRegistryConfig(folder2);
90077
87234
  const plugins2 = configuredFolderServerRegistry(await basePlugins, pluginConfig);
90078
87235
  const plugin2 = plugins2.get(pluginId);
90079
87236
  if (!plugin2)
90080
87237
  throw new Error("Unknown plugin");
90081
87238
  watch2(folder2);
90082
- const subpath = url3.searchParams.get("subpath") ?? "";
87239
+ const subpath = url2.searchParams.get("subpath") ?? "";
90083
87240
  if (!lockedRoot && !isMachineRoot(folder2) && request.headers["x-kennel-retry-linked"] === "1")
90084
87241
  linkedDiscovery.retry(folder2);
90085
87242
  const discovery = pluginId === "agents" || pluginId === "tasks" ? linkedState(folder2) : undefined;
@@ -90126,21 +87283,15 @@ function createFolderApi({
90126
87283
  }
90127
87284
  return;
90128
87285
  }
90129
- if (await handleMailApiRoutes(request, response, {
90130
- authorizeFolder: (value) => folderPath(value, root, lockedRoot),
90131
- mailDir,
90132
- mailos,
90133
- mailPreviewer,
90134
- watch: watch2
90135
- }))
90136
- return;
90137
- if (await handleTasksApiRoutes(request, response, {
90138
- authorizeFolder: (value) => folderPath(value, root, lockedRoot),
90139
- readOnly: remote,
90140
- watch: watch2
90141
- }))
90142
- return;
90143
- if (url3.pathname === "/api/create-agent-file") {
87286
+ for (const handler of await baseExternalApiHandlers) {
87287
+ if (await handler(request, response, {
87288
+ authorizeFolder: (value) => folderPath(value, root, lockedRoot),
87289
+ readOnly: remote,
87290
+ watch: watch2
87291
+ }))
87292
+ return;
87293
+ }
87294
+ if (url2.pathname === "/api/create-agent-file") {
90144
87295
  if (remote) {
90145
87296
  response.writeHead(403, {
90146
87297
  "Content-Type": "text/plain; charset=utf-8"
@@ -90148,12 +87299,12 @@ function createFolderApi({
90148
87299
  response.end("Host actions are disabled for remote access");
90149
87300
  return;
90150
87301
  }
90151
- await runApiRoute(request, response, url3, createAgentFileRoute, {
87302
+ await runApiRoute(request, response, url2, createAgentFileRoute, {
90152
87303
  resolve: (value) => folderPath(value, root, lockedRoot)
90153
87304
  });
90154
87305
  return;
90155
87306
  }
90156
- if (url3.pathname === "/api/open-path") {
87307
+ if (url2.pathname === "/api/open-path") {
90157
87308
  if (remote) {
90158
87309
  response.writeHead(403, {
90159
87310
  "Content-Type": "text/plain; charset=utf-8"
@@ -90161,25 +87312,25 @@ function createFolderApi({
90161
87312
  response.end("Host actions are disabled for remote access");
90162
87313
  return;
90163
87314
  }
90164
- await runApiRoute(request, response, url3, openPathRoute, {
87315
+ await runApiRoute(request, response, url2, openPathRoute, {
90165
87316
  resolve: (value) => folderPath(value, root, lockedRoot)
90166
87317
  });
90167
87318
  return;
90168
87319
  }
90169
- if (url3.pathname !== "/api/metadata") {
87320
+ if (url2.pathname !== "/api/metadata") {
90170
87321
  next();
90171
87322
  return;
90172
87323
  }
90173
87324
  try {
90174
- const part = url3.searchParams.get("part");
90175
- const folder2 = folderPath(url3.searchParams.get("path"), root, lockedRoot);
87325
+ const part = url2.searchParams.get("part");
87326
+ const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
90176
87327
  if (lockedTarget && part !== "workspace") {
90177
87328
  if (part === "collections") {
90178
87329
  response.writeHead(404, { "Content-Type": "application/json" });
90179
87330
  response.end(JSON.stringify({ error: "Collection not found" }));
90180
87331
  return;
90181
87332
  }
90182
- sendContract(response, url3, {
87333
+ sendContract(response, url2, {
90183
87334
  assets: {},
90184
87335
  files: { items: [] },
90185
87336
  folder: folder2,
@@ -90195,30 +87346,30 @@ function createFolderApi({
90195
87346
  workspace2.breadcrumbs = await workspaceBreadcrumbIcons(workspace2.breadcrumbs ?? [], assetFiles);
90196
87347
  const files = [
90197
87348
  workspace2.configPath,
90198
- resolve18(workspace2.root, "raggle.json")
87349
+ resolve12(workspace2.root, "raggle.json")
90199
87350
  ].filter((path) => Boolean(path));
90200
87351
  for (const path of files)
90201
87352
  contexts.add(path);
90202
87353
  server.watch(files);
90203
- sendContract(response, url3, { folder: folder2, workspace: workspace2 }, { "Cache-Control": "private, max-age=0, must-revalidate" });
87354
+ sendContract(response, url2, { folder: folder2, workspace: workspace2 }, { "Cache-Control": "private, max-age=0, must-revalidate" });
90204
87355
  return;
90205
87356
  }
90206
87357
  watch2(folder2);
90207
87358
  if (part === "collections") {
90208
- const sort = url3.searchParams.get("sort");
87359
+ const sort = url2.searchParams.get("sort");
90209
87360
  await collectionServer.handleMetadataPart({
90210
87361
  folder: folder2,
90211
- id: url3.searchParams.get("id"),
87362
+ id: url2.searchParams.get("id"),
90212
87363
  response,
90213
- send: (body2, headers) => sendContract(response, url3, body2, headers),
87364
+ send: (body2, headers) => sendContract(response, url2, body2, headers),
90214
87365
  sort: sort === "alpha" || sort === "recent" ? sort : undefined,
90215
- url: url3
87366
+ url: url2
90216
87367
  });
90217
87368
  return;
90218
87369
  }
90219
87370
  const machine = isMachineRoot(folder2);
90220
87371
  if (part === "notes") {
90221
- const scope = url3.searchParams.get("notesScope");
87372
+ const scope = url2.searchParams.get("notesScope");
90222
87373
  const localFiles = scope === "linked" ? Promise.resolve({ items: [] }) : (machine ? shallowMetadata(folder2).then((data) => data.files.items) : listLocalMarkdownFiles({ folder: folder2, limit: 200 })).then((files2) => notesFiles(folder2, files2));
90223
87374
  const linkedFiles = async () => {
90224
87375
  if (scope === "local" || machine || lockedRoot)
@@ -90240,7 +87391,7 @@ function createFolderApi({
90240
87391
  localFiles,
90241
87392
  linkedFiles()
90242
87393
  ]);
90243
- sendContract(response, url3, {
87394
+ sendContract(response, url2, {
90244
87395
  files: {
90245
87396
  items: [
90246
87397
  ...files.items,
@@ -90256,14 +87407,14 @@ function createFolderApi({
90256
87407
  const registry2 = await basePlugins;
90257
87408
  const plugins2 = configuredFolderServerRegistry(registry2, pluginConfig);
90258
87409
  const configWarnings = pluginConfigWarnings(registry2, pluginConfig);
90259
- sendContract(response, url3, {
87410
+ sendContract(response, url2, {
90260
87411
  configWarnings,
90261
87412
  folder: folder2,
90262
87413
  plugins: await pluginAvailability(plugins2, folder2, lockedRoot || isMachineRoot(folder2) ? [] : linkedDiscovery.peek(folder2).folders, configWarnings)
90263
87414
  }, { "Cache-Control": "private, max-age=0, must-revalidate" });
90264
87415
  return;
90265
87416
  }
90266
- const respond = (body2) => sendContract(response, url3, body2, {
87417
+ const respond = (body2) => sendContract(response, url2, body2, {
90267
87418
  "Cache-Control": "private, max-age=0, must-revalidate"
90268
87419
  });
90269
87420
  if (part === "identity") {
@@ -90349,8 +87500,8 @@ class AcpProcessManager {
90349
87500
  const stderrRing = [];
90350
87501
  let exitResolver;
90351
87502
  let exitCalled = false;
90352
- const onExit = new Promise((resolve19) => {
90353
- exitResolver = resolve19;
87503
+ const onExit = new Promise((resolve13) => {
87504
+ exitResolver = resolve13;
90354
87505
  });
90355
87506
  const child = spawn(adapter.command, adapter.args, {
90356
87507
  env: process.env,
@@ -90378,8 +87529,8 @@ class AcpProcessManager {
90378
87529
  exitResolver
90379
87530
  };
90380
87531
  child.stderr.on("data", (chunk) => {
90381
- const text3 = chunk.toString("utf-8");
90382
- const lines = text3.split(`
87532
+ const text2 = chunk.toString("utf-8");
87533
+ const lines = text2.split(`
90383
87534
  `);
90384
87535
  for (const line of lines) {
90385
87536
  if (line.length === 0)
@@ -90428,11 +87579,11 @@ class AcpProcessManager {
90428
87579
  if (slot.state === "stopping" || slot.state === "stopped" || slot.state === "errored")
90429
87580
  return;
90430
87581
  slot.state = "stopping";
90431
- return new Promise((resolve19, reject) => {
90432
- slot.stopResolver = resolve19;
87582
+ return new Promise((resolve13, reject) => {
87583
+ slot.stopResolver = resolve13;
90433
87584
  const killed = slot.process.kill("SIGTERM");
90434
87585
  if (!killed) {
90435
- resolve19();
87586
+ resolve13();
90436
87587
  return;
90437
87588
  }
90438
87589
  const escalate = setTimeout(() => {
@@ -92902,17 +90053,17 @@ function isResponseMessage(value) {
92902
90053
  function isNotificationMessage(value) {
92903
90054
  return isJsonRpcEnvelope(value) && !("id" in value) && typeof value["method"] === "string";
92904
90055
  }
92905
- function isRecord3(value) {
90056
+ function isRecord(value) {
92906
90057
  return typeof value === "object" && value !== null;
92907
90058
  }
92908
90059
  function isJsonRpcEnvelope(value) {
92909
- return isRecord3(value) && value["jsonrpc"] === "2.0";
90060
+ return isRecord(value) && value["jsonrpc"] === "2.0";
92910
90061
  }
92911
90062
  function isJsonRpcId(value) {
92912
90063
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
92913
90064
  }
92914
90065
  function isResponseShapedMessage(value) {
92915
- return isRecord3(value) && !("method" in value) && (("id" in value) || ("result" in value) || ("error" in value));
90066
+ return isRecord(value) && !("method" in value) && (("id" in value) || ("result" in value) || ("error" in value));
92916
90067
  }
92917
90068
  function isResponseBatch(batch) {
92918
90069
  let hasValidCall = false;
@@ -92922,7 +90073,7 @@ function isResponseBatch(batch) {
92922
90073
  for (const entry of batch) {
92923
90074
  hasValidCall ||= isRequestMessage(entry) || isNotificationMessage(entry);
92924
90075
  hasValidResponse ||= isResponseMessage(entry);
92925
- if (!isRecord3(entry)) {
90076
+ if (!isRecord(entry)) {
92926
90077
  continue;
92927
90078
  }
92928
90079
  hasCallShape ||= "method" in entry;
@@ -92937,13 +90088,13 @@ function isResponseBatch(batch) {
92937
90088
  return hasResponseShape && !hasCallShape;
92938
90089
  }
92939
90090
  function cancelRequestId(params) {
92940
- if (!isRecord3(params) || !isJsonRpcId(params["requestId"])) {
90091
+ if (!isRecord(params) || !isJsonRpcId(params["requestId"])) {
92941
90092
  return;
92942
90093
  }
92943
90094
  return params["requestId"];
92944
90095
  }
92945
90096
  function isErrorResponse(value) {
92946
- return isRecord3(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
90097
+ return isRecord(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
92947
90098
  }
92948
90099
  var Handled = {
92949
90100
  yes() {
@@ -93225,11 +90376,11 @@ class Connection {
93225
90376
  prepareRequest(method, params, mapResponse, options = {}) {
93226
90377
  const id = this.nextRequestId++;
93227
90378
  let cancel = () => {};
93228
- const response = new Promise((resolve19, reject) => {
90379
+ const response = new Promise((resolve13, reject) => {
93229
90380
  const pendingResponse = {
93230
90381
  resolve: (value) => {
93231
90382
  try {
93232
- resolve19(mapResponse ? mapResponse(value) : value);
90383
+ resolve13(mapResponse ? mapResponse(value) : value);
93233
90384
  } catch (error51) {
93234
90385
  reject(error51);
93235
90386
  }
@@ -93280,8 +90431,8 @@ class Connection {
93280
90431
  this.stream = stream;
93281
90432
  this.staticHandlers = handlers;
93282
90433
  this.allowBatches = options?.allowBatches ?? true;
93283
- this.closedPromise = new Promise((resolve19) => {
93284
- this.abortController.signal.addEventListener("abort", () => resolve19());
90434
+ this.closedPromise = new Promise((resolve13) => {
90435
+ this.abortController.signal.addEventListener("abort", () => resolve13());
93285
90436
  });
93286
90437
  this.receive();
93287
90438
  }
@@ -93338,7 +90489,7 @@ class Connection {
93338
90489
  this.receiveBatch(message);
93339
90490
  return;
93340
90491
  }
93341
- if (!isRecord3(message)) {
90492
+ if (!isRecord(message)) {
93342
90493
  console.error("Invalid message", { message });
93343
90494
  return;
93344
90495
  }
@@ -93399,7 +90550,7 @@ class Connection {
93399
90550
  if (this.abortController.signal.aborted) {
93400
90551
  return Promise.resolve();
93401
90552
  }
93402
- if (!isRecord3(message)) {
90553
+ if (!isRecord(message)) {
93403
90554
  console.error("Invalid message", { message });
93404
90555
  return Promise.resolve();
93405
90556
  }
@@ -93706,7 +90857,7 @@ function ndJsonStream(output, input) {
93706
90857
  if (trimmedLine) {
93707
90858
  try {
93708
90859
  const message = JSON.parse(trimmedLine);
93709
- if (isRecord3(message) || Array.isArray(message)) {
90860
+ if (isRecord(message) || Array.isArray(message)) {
93710
90861
  controller.enqueue(message);
93711
90862
  } else {
93712
90863
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -94085,8 +91236,8 @@ class AsyncQueue {
94085
91236
  if (this.failed) {
94086
91237
  return Promise.reject(this.failure);
94087
91238
  }
94088
- return new Promise((resolve19, reject) => {
94089
- this.waiters.push({ resolve: resolve19, reject });
91239
+ return new Promise((resolve13, reject) => {
91240
+ this.waiters.push({ resolve: resolve13, reject });
94090
91241
  });
94091
91242
  }
94092
91243
  }
@@ -94639,8 +91790,8 @@ async function bodyJson(request) {
94639
91790
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
94640
91791
  return raw ? JSON.parse(raw) : {};
94641
91792
  }
94642
- function reply(response, status2, body, ct = "application/json; charset=utf-8") {
94643
- response.writeHead(status2, {
91793
+ function reply(response, status, body, ct = "application/json; charset=utf-8") {
91794
+ response.writeHead(status, {
94644
91795
  "Content-Type": ct,
94645
91796
  "Cache-Control": "no-store"
94646
91797
  });
@@ -94742,19 +91893,19 @@ function createAcpApi({
94742
91893
  name: "kennel-acp-live",
94743
91894
  attach(server) {
94744
91895
  server.use(async (request, response, next) => {
94745
- const url3 = new URL(request.url ?? "/", "http://127.0.0.1");
91896
+ const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
94746
91897
  const method = request.method ?? "GET";
94747
- if (!url3.pathname.startsWith("/api/acp/"))
91898
+ if (!url2.pathname.startsWith("/api/acp/"))
94748
91899
  return next();
94749
91900
  if (remoteAccess) {
94750
91901
  reply(response, 403, "ACP live sessions are disabled for remote access", "text/plain");
94751
91902
  return;
94752
91903
  }
94753
- if (url3.pathname.startsWith("/api/acp/") && !loopbackHost(request.headers.host)) {
91904
+ if (url2.pathname.startsWith("/api/acp/") && !loopbackHost(request.headers.host)) {
94754
91905
  reply(response, 403, "Untrusted Host header", "text/plain");
94755
91906
  return;
94756
91907
  }
94757
- const streamMatch = /^\/api\/acp\/stream\/([a-f0-9-]+)$/.exec(url3.pathname);
91908
+ const streamMatch = /^\/api\/acp\/stream\/([a-f0-9-]+)$/.exec(url2.pathname);
94758
91909
  if (streamMatch?.[1] && method === "GET") {
94759
91910
  const s = sessions.get(streamMatch[1]);
94760
91911
  if (!s)
@@ -94779,7 +91930,7 @@ function createAcpApi({
94779
91930
  });
94780
91931
  return;
94781
91932
  }
94782
- if (url3.pathname === "/api/acp/connect" && method === "POST") {
91933
+ if (url2.pathname === "/api/acp/connect" && method === "POST") {
94783
91934
  if (guardMutation(request, response))
94784
91935
  return;
94785
91936
  try {
@@ -94795,18 +91946,18 @@ function createAcpApi({
94795
91946
  });
94796
91947
  }
94797
91948
  }
94798
- if (url3.pathname === "/api/acp/send" && method === "POST") {
91949
+ if (url2.pathname === "/api/acp/send" && method === "POST") {
94799
91950
  if (guardMutation(request, response))
94800
91951
  return;
94801
- const { sessionId, text: text3 } = await bodyJson(request);
94802
- if (!sessionId || !text3)
91952
+ const { sessionId, text: text2 } = await bodyJson(request);
91953
+ if (!sessionId || !text2)
94803
91954
  return reply(response, 400, "Missing sessionId or text", "text/plain");
94804
91955
  const s = sessions.get(String(sessionId));
94805
91956
  if (!s?.transport || !s.acpId)
94806
91957
  return reply(response, 404, "Session not found", "text/plain");
94807
91958
  s.transport.agent.request("session/prompt", {
94808
91959
  sessionId: s.acpId,
94809
- prompt: [{ type: "text", text: text3 }]
91960
+ prompt: [{ type: "text", text: text2 }]
94810
91961
  }).then((result) => {
94811
91962
  const data = result;
94812
91963
  if (data?.content) {
@@ -94840,7 +91991,7 @@ function createAcpApi({
94840
91991
  });
94841
91992
  return reply(response, 202, { accepted: true });
94842
91993
  }
94843
- if (url3.pathname === "/api/acp/cancel" && method === "POST") {
91994
+ if (url2.pathname === "/api/acp/cancel" && method === "POST") {
94844
91995
  if (guardMutation(request, response))
94845
91996
  return;
94846
91997
  const { sessionId } = await bodyJson(request);
@@ -94853,7 +92004,7 @@ function createAcpApi({
94853
92004
  broadcast(s, { type: "cancelled" });
94854
92005
  return reply(response, 200, { cancelled: true });
94855
92006
  }
94856
- if (url3.pathname === "/api/acp/disconnect" && method === "POST") {
92007
+ if (url2.pathname === "/api/acp/disconnect" && method === "POST") {
94857
92008
  if (guardMutation(request, response))
94858
92009
  return;
94859
92010
  const { sessionId } = await bodyJson(request);
@@ -94863,7 +92014,7 @@ function createAcpApi({
94863
92014
  await destroySession(s);
94864
92015
  return reply(response, 200, { disconnected: true });
94865
92016
  }
94866
- if (url3.pathname === "/api/acp/resume" && method === "POST") {
92017
+ if (url2.pathname === "/api/acp/resume" && method === "POST") {
94867
92018
  if (guardMutation(request, response))
94868
92019
  return;
94869
92020
  const { sessionId, resumeId } = await bodyJson(request);
@@ -94884,7 +92035,7 @@ function createAcpApi({
94884
92035
  });
94885
92036
  }
94886
92037
  }
94887
- if (url3.pathname === "/api/acp/reconnect" && method === "POST") {
92038
+ if (url2.pathname === "/api/acp/reconnect" && method === "POST") {
94888
92039
  if (guardMutation(request, response))
94889
92040
  return;
94890
92041
  const { sessionId } = await bodyJson(request);
@@ -94943,7 +92094,6 @@ data: ${JSON.stringify(event.data)}
94943
92094
  }
94944
92095
  };
94945
92096
  const folder2 = createFolderApi({
94946
- mailDir: process.env.KENNEL_MAIL_DIR || undefined,
94947
92097
  ...options,
94948
92098
  remoteAccess: remote
94949
92099
  });
@@ -94967,8 +92117,8 @@ data: ${JSON.stringify(event.data)}
94967
92117
  response.end("Untrusted Host header");
94968
92118
  return;
94969
92119
  }
94970
- const url3 = new URL(request.url ?? "/", "http://localhost");
94971
- if (url3.pathname === "/api/events") {
92120
+ const url2 = new URL(request.url ?? "/", "http://localhost");
92121
+ if (url2.pathname === "/api/events") {
94972
92122
  if (request.method !== "GET") {
94973
92123
  response.writeHead(405);
94974
92124
  response.end();
@@ -94994,26 +92144,26 @@ data: ${JSON.stringify(event.data)}
94994
92144
  if (!pass)
94995
92145
  return;
94996
92146
  }
94997
- if (url3.pathname.startsWith("/api/")) {
92147
+ if (url2.pathname.startsWith("/api/")) {
94998
92148
  response.writeHead(404);
94999
92149
  response.end("Unknown API endpoint");
95000
92150
  return;
95001
92151
  }
95002
92152
  if (options.webRoot && (request.method === "GET" || request.method === "HEAD")) {
95003
- const root = await realpath8(options.webRoot);
95004
- let target = resolve19(root, `.${decodeURIComponent(url3.pathname)}`);
92153
+ const root = await realpath6(options.webRoot);
92154
+ let target = resolve13(root, `.${decodeURIComponent(url2.pathname)}`);
95005
92155
  const inside5 = (path) => {
95006
- const local2 = relative15(root, path);
95007
- return local2 !== ".." && !local2.startsWith("../") && !isAbsolute10(local2);
92156
+ const local2 = relative11(root, path);
92157
+ return local2 !== ".." && !local2.startsWith("../") && !isAbsolute7(local2);
95008
92158
  };
95009
92159
  if (!inside5(target)) {
95010
92160
  response.writeHead(403);
95011
92161
  response.end();
95012
92162
  return;
95013
92163
  }
95014
- if (!(await stat10(target).catch(() => null))?.isFile())
95015
- target = resolve19(root, "index.html");
95016
- target = await realpath8(target);
92164
+ if (!(await stat9(target).catch(() => null))?.isFile())
92165
+ target = resolve13(root, "index.html");
92166
+ target = await realpath6(target);
95017
92167
  if (!inside5(target)) {
95018
92168
  response.writeHead(403);
95019
92169
  response.end();
@@ -95033,7 +92183,7 @@ data: ${JSON.stringify(event.data)}
95033
92183
  "Content-Type": types[extname7(target)] ?? "application/octet-stream",
95034
92184
  "X-Content-Type-Options": "nosniff"
95035
92185
  });
95036
- response.end(request.method === "HEAD" ? undefined : await readFile15(target));
92186
+ response.end(request.method === "HEAD" ? undefined : await readFile10(target));
95037
92187
  return;
95038
92188
  }
95039
92189
  next();