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