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