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.
- 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 +29 -3
- package/dist/cli/src/plugins/external.js +78 -24
- 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 +29 -3
- package/dist/plugins/external.js +78 -24
- 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
package/dist/core/runtime.js
CHANGED
|
@@ -55493,8 +55493,8 @@ var require_dist4 = __commonJS((exports) => {
|
|
|
55493
55493
|
import {
|
|
55494
55494
|
createServer
|
|
55495
55495
|
} from "node:http";
|
|
55496
|
-
import { readFile as
|
|
55497
|
-
import { extname as extname7, isAbsolute as
|
|
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
|
|
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
|
|
70196
|
-
import { readdir as
|
|
70197
|
-
import { homedir as
|
|
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
|
|
70200
|
-
dirname as
|
|
70199
|
+
basename as basename7,
|
|
70200
|
+
dirname as dirname10,
|
|
70201
70201
|
extname as extname6,
|
|
70202
|
-
isAbsolute as
|
|
70203
|
-
relative as
|
|
70204
|
-
resolve as
|
|
70205
|
-
sep as
|
|
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
|
|
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
|
|
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
|
|
86661
|
-
import { readdir as
|
|
86662
|
-
import { homedir as
|
|
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
|
|
84852
|
+
dirname as dirname5,
|
|
86666
84853
|
extname as extname2,
|
|
86667
|
-
isAbsolute as
|
|
84854
|
+
isAbsolute as isAbsolute4,
|
|
86668
84855
|
join as join2,
|
|
86669
|
-
relative as
|
|
86670
|
-
resolve as
|
|
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 =
|
|
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(
|
|
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
|
|
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() || !
|
|
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 =
|
|
86751
|
-
return local === "" || !local.startsWith("..") && !
|
|
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 =
|
|
86759
|
-
const resolved = await
|
|
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) =>
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
85000
|
+
const document3 = parseMarkdown(await readFile4(filePath, "utf8"));
|
|
86814
85001
|
return {
|
|
86815
85002
|
content: document3.body,
|
|
86816
|
-
path:
|
|
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 =
|
|
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
|
|
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
|
|
86894
|
-
realpath as
|
|
86895
|
-
rename as
|
|
85080
|
+
readFile as readFile5,
|
|
85081
|
+
realpath as realpath4,
|
|
85082
|
+
rename as rename2,
|
|
86896
85083
|
stat as stat4,
|
|
86897
|
-
unlink as
|
|
86898
|
-
writeFile as
|
|
85084
|
+
unlink as unlink2,
|
|
85085
|
+
writeFile as writeFile2
|
|
86899
85086
|
} from "node:fs/promises";
|
|
86900
85087
|
import {
|
|
86901
|
-
dirname as
|
|
85088
|
+
dirname as dirname6,
|
|
86902
85089
|
extname as extname3,
|
|
86903
|
-
isAbsolute as
|
|
86904
|
-
relative as
|
|
86905
|
-
resolve as
|
|
86906
|
-
sep as
|
|
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
|
|
86925
|
-
const target = await
|
|
86926
|
-
const local =
|
|
86927
|
-
if (!local || local === ".." || local.startsWith(`..${
|
|
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
|
|
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 =
|
|
85129
|
+
const temporary = resolve7(dirname6(target), `.kennel-edit-${randomUUID2()}.tmp`);
|
|
86943
85130
|
try {
|
|
86944
|
-
await
|
|
85131
|
+
await writeFile2(temporary, content, {
|
|
86945
85132
|
flag: "wx",
|
|
86946
85133
|
mode: details.mode & 511
|
|
86947
85134
|
});
|
|
86948
|
-
if (await
|
|
85135
|
+
if (await readFile5(target, "utf8") !== original)
|
|
86949
85136
|
throw apiError("conflict", "This note changed on disk. Reload before applying an edit.");
|
|
86950
|
-
await
|
|
85137
|
+
await rename2(temporary, target);
|
|
86951
85138
|
} finally {
|
|
86952
|
-
await
|
|
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
|
|
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
|
|
86982
|
-
import { resolve as
|
|
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 =
|
|
85180
|
+
const target = resolve8(folder2, "AGENTS.md");
|
|
86994
85181
|
try {
|
|
86995
|
-
await
|
|
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
|
|
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
|
|
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
|
|
87046
|
-
import { readFile as
|
|
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
|
|
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
|
|
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
|
|
87098
|
-
import { dirname as
|
|
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
|
|
87108
|
-
import { homedir as
|
|
87109
|
-
import { basename as basename5, dirname as
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
87356
|
-
if (await isRegularFile(
|
|
87357
|
-
found.push({ filename: basename5(
|
|
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
|
|
87363
|
-
if (await isRegularFile(
|
|
87364
|
-
return [{ filename: basename5(
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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 ??
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
88163
|
-
import { basename as
|
|
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
|
|
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 =
|
|
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:
|
|
88194
|
-
relativePath:
|
|
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" &&
|
|
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
|
|
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
|
|
88813
|
-
import { existsSync as
|
|
88814
|
-
import { createRequire as
|
|
88815
|
-
import { dirname as
|
|
88816
|
-
import { fileURLToPath
|
|
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 =
|
|
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 =
|
|
88839
|
-
while (
|
|
88840
|
-
if (
|
|
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
|
-
|
|
88843
|
-
|
|
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 =
|
|
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] ??
|
|
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((
|
|
88863
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
88953
|
-
return new Promise((
|
|
88954
|
-
|
|
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
|
-
|
|
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 =
|
|
86131
|
+
function createProjectDiscovery(load = discoverProjects) {
|
|
88971
86132
|
const jobs = new Map;
|
|
88972
|
-
let
|
|
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 =
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 =
|
|
89246
|
-
if (!candidate || candidate.startsWith("..") ||
|
|
86406
|
+
const candidate = relative10(root, path);
|
|
86407
|
+
if (!candidate || candidate.startsWith("..") || isAbsolute6(candidate))
|
|
89247
86408
|
return false;
|
|
89248
|
-
const parts = candidate.split(
|
|
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 =
|
|
86423
|
+
const folder2 = realpathSync2(resolve12(value ?? root));
|
|
89263
86424
|
if (!lockedRoot)
|
|
89264
86425
|
return folder2;
|
|
89265
|
-
const path =
|
|
89266
|
-
if (!path || !path.startsWith("..") && !
|
|
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
|
|
89272
|
-
|
|
89273
|
-
return `${
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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
|
-
|
|
89384
|
-
...ancestorFolders(folder2).map((ancestor) =>
|
|
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
|
-
|
|
89389
|
-
|
|
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 =
|
|
89398
|
-
const entries = await
|
|
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 =
|
|
89403
|
-
const skillPath =
|
|
89404
|
-
const details = await
|
|
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
|
|
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:
|
|
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 ===
|
|
86583
|
+
return folder2 === homedir4() || dirname10(folder2) === folder2;
|
|
89423
86584
|
}
|
|
89424
86585
|
async function shallowMetadata(folder2) {
|
|
89425
|
-
const entries = await
|
|
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 =
|
|
89429
|
-
const details = await
|
|
89430
|
-
const content = await
|
|
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:
|
|
86604
|
+
id: createHash5("sha1").update(resolve12(folder2, entry.name)).digest("hex"),
|
|
89444
86605
|
name: entry.name,
|
|
89445
86606
|
relativePath: entry.name,
|
|
89446
|
-
worktree:
|
|
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 ??
|
|
89474
|
-
relativePath:
|
|
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 ?
|
|
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
|
|
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(
|
|
89497
|
-
if (
|
|
89498
|
-
return
|
|
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 =
|
|
89519
|
-
const path =
|
|
86679
|
+
const rootReal = realpathSync2(resolve12(folder2));
|
|
86680
|
+
const path = resolve12(folder2, file3);
|
|
89520
86681
|
let pathReal;
|
|
89521
86682
|
try {
|
|
89522
|
-
pathReal =
|
|
86683
|
+
pathReal = realpathSync2(path);
|
|
89523
86684
|
} catch {
|
|
89524
86685
|
return;
|
|
89525
86686
|
}
|
|
89526
|
-
const rel =
|
|
89527
|
-
if (rel.startsWith("..") ||
|
|
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 =
|
|
89567
|
-
const path =
|
|
86727
|
+
const rootReal = realpathSync2(resolve12(folder2));
|
|
86728
|
+
const path = resolve12(folder2, file3);
|
|
89568
86729
|
let pathReal;
|
|
89569
86730
|
try {
|
|
89570
|
-
pathReal =
|
|
86731
|
+
pathReal = realpathSync2(path);
|
|
89571
86732
|
} catch {
|
|
89572
86733
|
return;
|
|
89573
86734
|
}
|
|
89574
|
-
const rel =
|
|
89575
|
-
if (rel.startsWith("..") ||
|
|
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 `"${
|
|
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 =
|
|
89621
|
-
const candidate =
|
|
89622
|
-
const details = await
|
|
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 (
|
|
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
|
|
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
|
|
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 ?
|
|
89684
|
-
const lockedTarget = lockedFile ?
|
|
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 ??
|
|
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:
|
|
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
|
|
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,
|
|
86917
|
+
if (!validateQuery(request, response, url2))
|
|
89761
86918
|
return;
|
|
89762
|
-
if (
|
|
89763
|
-
sendContract(response,
|
|
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 (
|
|
89773
|
-
const folder2 = folderPath(
|
|
89774
|
-
sendContract(response,
|
|
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 (
|
|
89778
|
-
sendContract(response,
|
|
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 (
|
|
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(
|
|
86942
|
+
const folder2 = folderPath(url2.searchParams.get("path"), root, lockedRoot);
|
|
89786
86943
|
const host = await folderWebsite(folder2);
|
|
89787
|
-
if (!host || 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(
|
|
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
|
|
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(
|
|
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 (
|
|
89842
|
-
const query =
|
|
89843
|
-
const
|
|
89844
|
-
projectSearchCache =
|
|
89845
|
-
const result = searchProjectIndex(
|
|
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,
|
|
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 (
|
|
87013
|
+
if (url2.pathname === "/api/entities") {
|
|
89857
87014
|
try {
|
|
89858
|
-
const workspace2 = await loadEntityWorkspace(
|
|
89859
|
-
sendContract(response,
|
|
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 (
|
|
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,
|
|
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 (
|
|
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(
|
|
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) =>
|
|
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 (
|
|
89898
|
-
const folder2 = folderPath(
|
|
89899
|
-
const target = lockedTarget ? documentTarget(folder2,
|
|
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:
|
|
87061
|
+
path: relative10(folder2, target.path),
|
|
89905
87062
|
kind: target.kind,
|
|
89906
|
-
content: previewTextKind(target.kind) ? await
|
|
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,
|
|
87068
|
+
sendContract(response, url2, dataset, { "Cache-Control": "no-store" });
|
|
89912
87069
|
return;
|
|
89913
87070
|
}
|
|
89914
|
-
if (
|
|
87071
|
+
if (url2.pathname === "/api/document") {
|
|
89915
87072
|
try {
|
|
89916
|
-
const folder2 = folderPath(
|
|
89917
|
-
const target = documentTarget(folder2,
|
|
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
|
|
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
|
|
89935
|
-
sendContract(response,
|
|
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:
|
|
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:
|
|
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 (
|
|
87112
|
+
if (url2.pathname === "/api/raw") {
|
|
89956
87113
|
try {
|
|
89957
|
-
const folder2 = folderPath(
|
|
89958
|
-
const target = documentTarget(folder2,
|
|
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
|
|
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 =
|
|
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="${
|
|
87141
|
+
"Content-Disposition": `attachment; filename="${basename7(target.path)}"`
|
|
89985
87142
|
} : {}
|
|
89986
87143
|
});
|
|
89987
|
-
response.end(await
|
|
87144
|
+
response.end(await readFile9(target.path));
|
|
89988
87145
|
} catch (error51) {
|
|
89989
87146
|
documentError(response);
|
|
89990
87147
|
}
|
|
89991
87148
|
return;
|
|
89992
87149
|
}
|
|
89993
|
-
if (
|
|
87150
|
+
if (url2.pathname === "/api/media") {
|
|
89994
87151
|
try {
|
|
89995
|
-
const folder2 = folderPath(
|
|
89996
|
-
const target = mediaTarget(folder2,
|
|
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
|
|
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
|
|
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 (
|
|
87207
|
+
if (url2.pathname === "/api/session") {
|
|
90051
87208
|
try {
|
|
90052
|
-
const 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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
90130
|
-
|
|
90131
|
-
|
|
90132
|
-
|
|
90133
|
-
|
|
90134
|
-
|
|
90135
|
-
|
|
90136
|
-
|
|
90137
|
-
if (
|
|
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,
|
|
87302
|
+
await runApiRoute(request, response, url2, createAgentFileRoute, {
|
|
90152
87303
|
resolve: (value) => folderPath(value, root, lockedRoot)
|
|
90153
87304
|
});
|
|
90154
87305
|
return;
|
|
90155
87306
|
}
|
|
90156
|
-
if (
|
|
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,
|
|
87315
|
+
await runApiRoute(request, response, url2, openPathRoute, {
|
|
90165
87316
|
resolve: (value) => folderPath(value, root, lockedRoot)
|
|
90166
87317
|
});
|
|
90167
87318
|
return;
|
|
90168
87319
|
}
|
|
90169
|
-
if (
|
|
87320
|
+
if (url2.pathname !== "/api/metadata") {
|
|
90170
87321
|
next();
|
|
90171
87322
|
return;
|
|
90172
87323
|
}
|
|
90173
87324
|
try {
|
|
90174
|
-
const part =
|
|
90175
|
-
const folder2 = folderPath(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
87359
|
+
const sort = url2.searchParams.get("sort");
|
|
90209
87360
|
await collectionServer.handleMetadataPart({
|
|
90210
87361
|
folder: folder2,
|
|
90211
|
-
id:
|
|
87362
|
+
id: url2.searchParams.get("id"),
|
|
90212
87363
|
response,
|
|
90213
|
-
send: (body2, headers) => sendContract(response,
|
|
87364
|
+
send: (body2, headers) => sendContract(response, url2, body2, headers),
|
|
90214
87365
|
sort: sort === "alpha" || sort === "recent" ? sort : undefined,
|
|
90215
|
-
url:
|
|
87366
|
+
url: url2
|
|
90216
87367
|
});
|
|
90217
87368
|
return;
|
|
90218
87369
|
}
|
|
90219
87370
|
const machine = isMachineRoot(folder2);
|
|
90220
87371
|
if (part === "notes") {
|
|
90221
|
-
const scope =
|
|
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,
|
|
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,
|
|
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,
|
|
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((
|
|
90353
|
-
exitResolver =
|
|
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
|
|
90382
|
-
const lines =
|
|
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((
|
|
90432
|
-
slot.stopResolver =
|
|
87582
|
+
return new Promise((resolve13, reject) => {
|
|
87583
|
+
slot.stopResolver = resolve13;
|
|
90433
87584
|
const killed = slot.process.kill("SIGTERM");
|
|
90434
87585
|
if (!killed) {
|
|
90435
|
-
|
|
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
|
|
90056
|
+
function isRecord(value) {
|
|
92906
90057
|
return typeof value === "object" && value !== null;
|
|
92907
90058
|
}
|
|
92908
90059
|
function isJsonRpcEnvelope(value) {
|
|
92909
|
-
return
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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((
|
|
90379
|
+
const response = new Promise((resolve13, reject) => {
|
|
93229
90380
|
const pendingResponse = {
|
|
93230
90381
|
resolve: (value) => {
|
|
93231
90382
|
try {
|
|
93232
|
-
|
|
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((
|
|
93284
|
-
this.abortController.signal.addEventListener("abort", () =>
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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((
|
|
94089
|
-
this.waiters.push({ resolve:
|
|
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,
|
|
94643
|
-
response.writeHead(
|
|
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
|
|
91896
|
+
const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
94746
91897
|
const method = request.method ?? "GET";
|
|
94747
|
-
if (!
|
|
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 (
|
|
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(
|
|
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 (
|
|
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 (
|
|
91949
|
+
if (url2.pathname === "/api/acp/send" && method === "POST") {
|
|
94799
91950
|
if (guardMutation(request, response))
|
|
94800
91951
|
return;
|
|
94801
|
-
const { sessionId, text:
|
|
94802
|
-
if (!sessionId || !
|
|
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:
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
94971
|
-
if (
|
|
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 (
|
|
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
|
|
95004
|
-
let target =
|
|
92153
|
+
const root = await realpath6(options.webRoot);
|
|
92154
|
+
let target = resolve13(root, `.${decodeURIComponent(url2.pathname)}`);
|
|
95005
92155
|
const inside5 = (path) => {
|
|
95006
|
-
const local2 =
|
|
95007
|
-
return local2 !== ".." && !local2.startsWith("../") && !
|
|
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
|
|
95015
|
-
target =
|
|
95016
|
-
target = await
|
|
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
|
|
92186
|
+
response.end(request.method === "HEAD" ? undefined : await readFile10(target));
|
|
95037
92187
|
return;
|
|
95038
92188
|
}
|
|
95039
92189
|
next();
|