@tequila99/githuman 0.7.0 → 0.9.0
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 +75 -0
- package/dist/cli/config.js +13 -1
- package/dist/cli/server-runtime.js +3 -0
- package/dist/server/app.js +37 -4
- package/dist/server/config/agents.js +2 -1
- package/dist/server/config/openapi.js +4 -0
- package/dist/server/db/migrations.js +61 -0
- package/dist/server/errors/note-assets.js +3 -0
- package/dist/server/errors/notes.js +3 -0
- package/dist/server/hooks/notes/same-origin.js +37 -0
- package/dist/server/hooks/terminal/local-origin.js +2 -1
- package/dist/server/repositories/notes-catalog.repo.js +129 -0
- package/dist/server/repositories/notes.repo.js +444 -0
- package/dist/server/routes/agent.js +1 -0
- package/dist/server/routes/notes.js +213 -0
- package/dist/server/routes/terminal.js +2 -1
- package/dist/server/services/agent/context-builder.js +13 -0
- package/dist/server/services/git.service.js +15 -11
- package/dist/server/services/notes-external-assets.service.js +177 -0
- package/dist/server/services/notes-identity.service.js +811 -0
- package/dist/server/services/notes-operations.service.js +296 -0
- package/dist/server/services/notes-watcher.service.js +44 -0
- package/dist/server/services/notes-writer.service.js +116 -0
- package/dist/server/services/notes.service.js +150 -0
- package/dist/server/services/terminal/backend.js +6 -3
- package/dist/server/services/terminal/channel.js +6 -3
- package/dist/server/services/terminal/registry.js +10 -5
- package/dist/server/services/terminal/session.js +10 -5
- package/dist/server/services/terminal/tokens.js +4 -2
- package/dist/server/utils/hash.js +4 -0
- package/dist/server/utils/note-asset-format.js +19 -0
- package/dist/server/utils/remote-asset-request.js +193 -0
- package/dist/server/utils/request-abort.js +20 -0
- package/dist/server/utils/system-error.js +14 -0
- package/dist/shared/agents/schemas.js +14 -0
- package/dist/shared/events/types.js +1 -0
- package/dist/shared/notes/asset-formats.js +57 -0
- package/dist/shared/notes/constants.js +7 -0
- package/dist/shared/notes/document.js +118 -0
- package/dist/shared/notes/external-assets.js +153 -0
- package/dist/shared/notes/identity.js +24 -0
- package/dist/shared/notes/links.js +44 -0
- package/dist/shared/notes/note-path.js +17 -0
- package/dist/shared/notes/reference-path.js +55 -0
- package/dist/shared/notes/references.js +190 -0
- package/dist/shared/notes/schemas.js +222 -0
- package/dist/shared/notes/types.js +1 -0
- package/dist/shared/utils/heading-slug.js +7 -0
- package/dist/shared/utils/is-uuid.js +5 -0
- package/dist/shared/utils/safe-decode.js +9 -0
- package/dist/shared/utils/web-url.js +15 -0
- package/dist/web/assets/AppPage-DdmqiV2R.js +1 -0
- package/dist/web/assets/ClosePopup-lpH_1Ju2.js +1 -0
- package/dist/web/assets/CreateButton-Dnpwobd9.js +93 -0
- package/dist/web/assets/CreateReviewFabButton-KvKPEJzI.js +1 -0
- package/dist/web/assets/{ErrorNotFound-CYOZg8yN.js → ErrorNotFound-Cq0wNQMu.js} +1 -1
- package/dist/web/assets/IndexPage-DE-IkAhK.js +1 -0
- package/dist/web/assets/IndexPage-F8MMAAkn.css +1 -0
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-gV2CO0n9.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/MainLayout-DM30ynlR.css +1 -0
- package/dist/web/assets/MainLayout-wWA8ckBg.js +6 -0
- package/dist/web/assets/NoteVisualEditor-Bj13_oEl.js +1054 -0
- package/dist/web/assets/NoteVisualEditor-IH3s3d3W.css +1 -0
- package/dist/web/assets/NotesPage-FidBg83M.js +4 -0
- package/dist/web/assets/NotesPage-KaCEnfdz.css +1 -0
- package/dist/web/assets/PanelResize-C51WwwLD.css +1 -0
- package/dist/web/assets/PanelResize-DY0Nzmql.js +1 -0
- package/dist/web/assets/{PdfPreview-CIiKnzQN.js → PdfPreview-C7HomxEg.js} +1 -1
- package/dist/web/assets/QBtn-DoggJe1Q.js +1 -0
- package/dist/web/assets/QFab-C-YZWgmW.js +1 -0
- package/dist/web/assets/QForm-DpEhYVCa.js +4 -0
- package/dist/web/assets/QInput-DJgTh2Qy.js +1 -0
- package/dist/web/assets/QResizeObserver-DeRFR_WF.js +1 -0
- package/dist/web/assets/QScrollArea-BSSZoaV5.js +1 -0
- package/dist/web/assets/QSelect-2iOBkp1K.js +1 -0
- package/dist/web/assets/QSpace-B5NneHAd.js +1 -0
- package/dist/web/assets/QVirtualScroll-CZJMBpmR.js +1 -0
- package/dist/web/assets/ReviewDetailPage-BI1iAhSf.js +1 -0
- package/dist/web/assets/ReviewsPage-RyST0FlH.js +1 -0
- package/dist/web/assets/{StatusSwitcher-DTD5inU1.css → StatusSwitcher-BCv_9MtI.css} +1 -1
- package/dist/web/assets/StatusSwitcher-DAeNzNP7.js +1 -0
- package/dist/web/assets/{TerminalView-Ch-ndYr6.js → TerminalView-BAzJgUTq.js} +2 -2
- package/dist/web/assets/{TerminalView-BurzSD2L.css → TerminalView-C1jFlfLH.css} +1 -1
- package/dist/web/assets/_baseMerge-BzZFQ6mI.js +1 -0
- package/dist/web/assets/{abnfDiagram-O67JEVCF-Cab71FlB.js → abnfDiagram-O67JEVCF-BNZQ7duT.js} +1 -1
- package/dist/web/assets/agent-store-DP6Vrw6L.js +113 -0
- package/dist/web/assets/apl-gfMMiTNf.js +1 -0
- package/dist/web/assets/app-info-store-u6ZVFjv5.js +1 -0
- package/dist/web/assets/architecture-7GRP2DOG-BSdnjp9Z.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-Dl1WKOEi.js → architectureDiagram-NJMV4G6O-Bkj0kfFn.js} +1 -1
- package/dist/web/assets/asciiarmor-C_eisQCp.js +1 -0
- package/dist/web/assets/asn1-DCl_8MCW.js +1 -0
- package/dist/web/assets/asterisk-BEn0Cwpx.js +1 -0
- package/dist/web/assets/{blockDiagram-BEXU5L5S-CZ3Lxybj.js → blockDiagram-BEXU5L5S-CXO3zN2c.js} +1 -1
- package/dist/web/assets/brainfuck-D5EjA2JK.js +1 -0
- package/dist/web/assets/{c4Diagram-YGBWAQC7-ULi06PEd.js → c4Diagram-YGBWAQC7-BHfMeZNf.js} +1 -1
- package/dist/web/assets/ccount-CcG1m-Yh.js +1 -0
- package/dist/web/assets/channel-DmoOM6AB.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-B1f60wx3.js → chunk-3FUC2YCW-z1WbH7Gi.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-f6Pcl3dF.js → chunk-5DYCD2WN-K9HPch97.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-szX1QirW.js → chunk-742MDFTN-Bhjp9fC-.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-CP_ynmAQ.js → chunk-7INBJB4K-Do0EmU14.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-DPL74uZE.js → chunk-7M6MHVWA-veeEjsOh.js} +5 -5
- package/dist/web/assets/{chunk-7PRAP22T-DH9TawZY.js → chunk-7PRAP22T-CPqB5tCK.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-CKAuGvnf.js → chunk-GTNCS2PH-Civ1V52P.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-DTbHCHSN.js → chunk-GWA4HPMP-C7LzAlyZ.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-ClgvJMtC.js → chunk-MBY4JIJT-DfmJy0o_.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-Dq5Z7z8S.js → chunk-NETBCI7D-BZIKmMvL.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-C0By5mcm.js → chunk-O7XYJQB3-DcHeu6b7.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-BtquYpxe.js → chunk-UA2S7LBM-0SR2og6x.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-BlZL42XJ.js → chunk-WEXAMYUT-CJMSoM-Z.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BbtQbAvv.js → chunk-Z7XXMR3K-q6fce66x.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS-tC_YdRrm.js → chunk-ZIGJFQKS-DsPzen4Y.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-CvlA_A4N.js → classDiagram-v2-NBCMYWYE-Cpm_bvzb.js} +4 -4
- package/dist/web/assets/clike-D_6L3tnO.js +1 -0
- package/dist/web/assets/clojure-BflJGmDX.js +1 -0
- package/dist/web/assets/cmake-84nbDH0I.js +1 -0
- package/dist/web/assets/cobol-DHLQUbN7.js +1 -0
- package/dist/web/assets/coffeescript-De_zI4J0.js +1 -0
- package/dist/web/assets/commonlisp-DgAKBe0r.js +1 -0
- package/dist/web/assets/core-Bumj8M77.js +12 -0
- package/dist/web/assets/crystal-Bsdphhtm.js +1 -0
- package/dist/web/assets/css-CnaYoQib.js +1 -0
- package/dist/web/assets/{cynefin-OW5HDTMX-DFbhKkIi.js → cynefin-OW5HDTMX-ZTr5XZMW.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-DHGaLNQN.js → cynefinDiagram-VND7K2PF-ByUmUx58.js} +1 -1
- package/dist/web/assets/cypher-p9eYesGn.js +1 -0
- package/dist/web/assets/d-DWkQyNnU.js +1 -0
- package/dist/web/assets/dagre-6A5THRUB-mQOy0E3r.js +4 -0
- package/dist/web/assets/{diagram-22UHCM2B-Djjo5ftL.js → diagram-22UHCM2B-BW0LPtyk.js} +8 -8
- package/dist/web/assets/{diagram-3UASUU5V-BcP7pJru.js → diagram-3UASUU5V-jo148ec5.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-o_vsc8em.js → diagram-ATOU4E4O-CJt4F0i4.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-eyN9Aru8.js → diagram-CDSNMT55-DEShJv56.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-Dfjipnvb.js → diagram-MLGK6HIB-DOFOADfs.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-C2chocRe.js → diagram-MPIPVDR6-ux537BMl.js} +1 -1
- package/dist/web/assets/diff-ChtP43wD.js +1 -0
- package/dist/web/assets/dist-3B0OaUZ-.js +1 -0
- package/dist/web/assets/dist-7xWvOKEv.js +1 -0
- package/dist/web/assets/dist-BThMRW5U.js +23 -0
- package/dist/web/assets/dist-BWa3ecCV.js +2 -0
- package/dist/web/assets/dist-BY24_62d.js +1 -0
- package/dist/web/assets/dist-BbdrgL5V.js +11 -0
- package/dist/web/assets/dist-Bo7anpPj.js +9 -0
- package/dist/web/assets/dist-Bu9yRqTU.js +1 -0
- package/dist/web/assets/dist-Bvnostf5.js +1 -0
- package/dist/web/assets/dist-C3sBgvRy.js +1 -0
- package/dist/web/assets/dist-CK1va5C_.js +1 -0
- package/dist/web/assets/dist-CLv-grWO.js +1 -0
- package/dist/web/assets/dist-CqGqStPp.js +1 -0
- package/dist/web/assets/dist-D1EUrS6y.js +6 -0
- package/dist/web/assets/dist-D2Ce45aH.js +13 -0
- package/dist/web/assets/dist-D7Llw4iH.js +1 -0
- package/dist/web/assets/dist-DL0dBIkt.js +1 -0
- package/dist/web/assets/dist-DUt0feNB.js +1 -0
- package/dist/web/assets/dist-DZQ2IsS9.js +1 -0
- package/dist/web/assets/dist-DpKJcdvz.js +1 -0
- package/dist/web/assets/dist-DuZv0LJj.js +1 -0
- package/dist/web/assets/dist-LFPPEX39.js +1 -0
- package/dist/web/assets/dist-lC0EKTFH.js +1 -0
- package/dist/web/assets/dockerfile-DJkHQgkl.js +1 -0
- package/dist/web/assets/dtd-DTF72YNO.js +1 -0
- package/dist/web/assets/dylan-BCDoL_-p.js +1 -0
- package/dist/web/assets/ebnf-D4c0_ac3.js +1 -0
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CE58dwC-.js → ebnfDiagram-ZINNZB2B-DEybuZle.js} +1 -1
- package/dist/web/assets/ecl-BVIeWmwY.js +1 -0
- package/dist/web/assets/eiffel-BEjRio4Q.js +1 -0
- package/dist/web/assets/{elk-276RUBZZ-KyTn9aJi.js → elk-276RUBZZ-CZXZRpGb.js} +1 -1
- package/dist/web/assets/elm-Cshzl8qu.js +1 -0
- package/dist/web/assets/{erDiagram-OPXOYQCR-D9aOi7ZD.js → erDiagram-OPXOYQCR-BgRqGAt5.js} +1 -1
- package/dist/web/assets/erlang-BV0c4Hdn.js +1 -0
- package/dist/web/assets/eventmodeling-NTZA5JFV-ZuRq6Sae.js +1 -0
- package/dist/web/assets/factor-LKH5gN8L.js +1 -0
- package/dist/web/assets/fcl-ClOhbFR7.js +1 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-DRyHAdLb.js +1 -0
- package/dist/web/assets/forth-D6RzAl64.js +1 -0
- package/dist/web/assets/fortran-Bt6PBEDR.js +1 -0
- package/dist/web/assets/{ganttDiagram-FUAMR5RP-DRAI6xWM.js → ganttDiagram-FUAMR5RP-CBJwX1gk.js} +1 -1
- package/dist/web/assets/gas-BHEdbvp9.js +1 -0
- package/dist/web/assets/gherkin-oBAE_ms0.js +1 -0
- package/dist/web/assets/{gitGraph-4MIJSDKK-D1lAB6tr.js → gitGraph-4MIJSDKK-Dw-XA3G1.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-77GVnwya.js → gitGraphDiagram-X574FWY7-BpGccoWp.js} +1 -1
- package/dist/web/assets/graphlib-C64HifUL.js +1 -0
- package/dist/web/assets/groovy-CUpZ7dEl.js +1 -0
- package/dist/web/assets/haskell-BRsoo5mP.js +1 -0
- package/dist/web/assets/haxe-DjVOVvNP.js +1 -0
- package/dist/web/assets/http-CLVgA2GD.js +1 -0
- package/dist/web/assets/i18n-sfJvmpVb.js +1 -0
- package/dist/web/assets/idl-BUZw3wgd.js +1 -0
- package/dist/web/assets/index-B2CzafCr.js +2 -0
- package/dist/web/assets/{info-A6RAGUB7-Di1qRpwO.js → info-A6RAGUB7-Dj5RfJsg.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-gceHneJ0.js → infoDiagram-VRGFBTTK-DyVSH991.js} +1 -1
- package/dist/web/assets/{init-app-IHgDyoRH.js → init-app-DSS44aRI.js} +1 -1
- package/dist/web/assets/{is-DssfAlMe.js → is-CE4jkrGQ.js} +1 -1
- package/dist/web/assets/isArrayLikeObject-BbaDyxId.js +1 -0
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-B9QDQoSG.js → ishikawaDiagram-OU5B5YK6-Cu0n2oqT.js} +1 -1
- package/dist/web/assets/javascript-Bu8mWwtz.js +1 -0
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-CVTMW0pg.js → journeyDiagram-ZHPQQLJL-Bz1Ot6Zg.js} +1 -1
- package/dist/web/assets/julia-CQfgDRfP.js +1 -0
- package/dist/web/assets/{kanban-definition-PNTS6WVX-TNoWYrUr.js → kanban-definition-PNTS6WVX-C42vuY7H.js} +1 -1
- package/dist/web/assets/{line-B_1CHIjZ.js → line-C5WKurbS.js} +1 -1
- package/dist/web/assets/livescript-BXxG0Yva.js +1 -0
- package/dist/web/assets/lua-DPCEkfD_.js +1 -0
- package/dist/web/assets/markdown-DBTEhbhQ.js +15 -0
- package/dist/web/assets/mathematica-u8YMmbU0.js +1 -0
- package/dist/web/assets/mbox-BEMVcqjs.js +1 -0
- package/dist/web/assets/{mermaid-parser.core-LAeoGxkL.js → mermaid-parser.core-B7l8qh8B.js} +3 -3
- package/dist/web/assets/{mermaid.core-DMppSzfM.js → mermaid.core-Drs2mjGg.js} +7 -7
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-DJ-6nTWg.js → mindmap-definition-NLK3R4M7-BV1FNwCn.js} +1 -1
- package/dist/web/assets/mirc-C9zpzAZU.js +1 -0
- package/dist/web/assets/mllike-B45xgp2S.js +1 -0
- package/dist/web/assets/modelica-CqHI_q-D.js +1 -0
- package/dist/web/assets/mscgen-qgSLujhx.js +1 -0
- package/dist/web/assets/mumps-BSzj6Xyz.js +1 -0
- package/dist/web/assets/nginx-8H7HVH4w.js +1 -0
- package/dist/web/assets/nsis-viPvXFiq.js +1 -0
- package/dist/web/assets/ntriples-B9h1VB_g.js +1 -0
- package/dist/web/assets/octave-D2Q8cUp1.js +1 -0
- package/dist/web/assets/oz-BwTct_5T.js +1 -0
- package/dist/web/assets/{packet-AYTQ26CC-B4tvN4nf.js → packet-AYTQ26CC-BPmUY1Ig.js} +1 -1
- package/dist/web/assets/pascal-Dp_KdFgX.js +1 -0
- package/dist/web/assets/{pegDiagram-GJSIUBJH-BwQ6jaVw.js → pegDiagram-GJSIUBJH-D6NPxPdr.js} +1 -1
- package/dist/web/assets/perl-BkSkiI9m.js +1 -0
- package/dist/web/assets/{pie-WAS4IAKB-ZwlyCxls.js → pie-WAS4IAKB-CTp2jNAc.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-BDYLBnB1.js → pieDiagram-5QR66LMP-BVkABrg7.js} +1 -1
- package/dist/web/assets/pig-AF4Hwhju.js +1 -0
- package/dist/web/assets/pinia-BVWaJ8NX.js +1 -0
- package/dist/web/assets/powershell-BcIto6Sf.js +1 -0
- package/dist/web/assets/properties-C357ku5U.js +1 -0
- package/dist/web/assets/protobuf-D1ij_kNL.js +1 -0
- package/dist/web/assets/pug-3K-JMW4g.js +1 -0
- package/dist/web/assets/puppet-BZyAS7I4.js +1 -0
- package/dist/web/assets/purify.es--06brOip.js +3 -0
- package/dist/web/assets/python-B_1Jd8HM.js +1 -0
- package/dist/web/assets/q-B9NhS7L_.js +1 -0
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-CkmLCxOG.js → quadrantDiagram-O4NWA36T-LZCjVd08.js} +1 -1
- package/dist/web/assets/r-CxDmU1I4.js +1 -0
- package/dist/web/assets/{radar-RG4KPBEZ-Ca7H5QOY.js → radar-RG4KPBEZ-CCiUrmsg.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-CX3Z2QYJ.js → railroad-74A4TZTK-CZn8VJbK.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-B-9tG9pW.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-D7lUyGOq.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-ja94t32K.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-D8ii6sM4.js → railroadDiagram-XR7U4H2S-nopy-mdw.js} +1 -1
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-iHY-tE7c.js → requirementDiagram-PLB6GJNP-BOeMzf-x.js} +1 -1
- package/dist/web/assets/rpm-gpDK5sbt.js +1 -0
- package/dist/web/assets/ruby-ChfNwlCY.js +1 -0
- package/dist/web/assets/{safe-storage-BCP757jk.js → safe-storage-aRoyeFxv.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-CBprSUZk.js → sankeyDiagram-IPEJSGJF-Bbk0AQUe.js} +1 -1
- package/dist/web/assets/sas-C2OyD2Y6.js +1 -0
- package/dist/web/assets/scheme-D9qYN0V5.js +1 -0
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-BkphpOQ_.js → sequenceDiagram-PO4LG4MO-ZpubjKFc.js} +1 -1
- package/dist/web/assets/shell-DwuoZtxw.js +1 -0
- package/dist/web/assets/sieve-tLWDfEkZ.js +1 -0
- package/dist/web/assets/simple-mode-DXiLM1-G.js +1 -0
- package/dist/web/assets/smalltalk-BLp5-jwC.js +1 -0
- package/dist/web/assets/solr-B78wSQJQ.js +1 -0
- package/dist/web/assets/sparql-Dz-mlCAC.js +1 -0
- package/dist/web/assets/spreadsheet-CTIncYxj.js +1 -0
- package/dist/web/assets/sql-DuQMFDvy.js +1 -0
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-HN1RwAPZ.js → stateDiagram-v2-GCMORJYK-DNjDh9yZ.js} +2 -2
- package/dist/web/assets/stex-DNzd62Q_.js +1 -0
- package/dist/web/assets/stylus-BOVgKnng.js +1 -0
- package/dist/web/assets/swift-DKB6_1j6.js +1 -0
- package/dist/web/assets/{swimlanes-2SLR337P-Cc8jyETz.js → swimlanes-2SLR337P-C-_UzZF_.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-CVSZNvS2.js +8 -0
- package/dist/web/assets/tcl-BGfruO1u.js +1 -0
- package/dist/web/assets/textile-C4jVoHG1.js +1 -0
- package/dist/web/assets/theme-CeQhGDjh.js +1 -0
- package/dist/web/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/dist/web/assets/tiki-AK6FwT8q.js +1 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-Bas0Ksw6.js → timeline-definition-EJHVYXUP-BiaJyHdJ.js} +1 -1
- package/dist/web/assets/toml-BPTmHmyx.js +1 -0
- package/dist/web/assets/{treeView-Q6P3EWNA-Ck-xt3KO.js → treeView-Q6P3EWNA-CENjlerX.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-k9Ay9d-c.js → treemap-WGGIJYW6-CBpdf3L2.js} +1 -1
- package/dist/web/assets/troff-C2dAgh7P.js +1 -0
- package/dist/web/assets/ttcn-PPSTk1ow.js +1 -0
- package/dist/web/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/dist/web/assets/turtle-Cx3rYX2g.js +1 -0
- package/dist/web/assets/use-add-agent-context-SApxn9fW.js +1 -0
- package/dist/web/assets/{use-app-theme-BfRqhHi_.js → use-app-theme-DP8Ymwwb.js} +1 -1
- package/dist/web/assets/use-dark-BHObfh0O.js +1 -0
- package/dist/web/assets/use-panel-width-CTXD_csP.js +1 -0
- package/dist/web/assets/use-router-link-BGc0noIO.js +1 -0
- package/dist/web/assets/use-server-events-C13Sw-rU.js +1 -0
- package/dist/web/assets/use-virtual-scroll-BSXYZjZ5.js +1 -0
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-BlzYgvXB.js → usecaseDiagram-POWQR4AR-CcJ83_Ve.js} +1 -1
- package/dist/web/assets/vb-BthdM_4N.js +1 -0
- package/dist/web/assets/vbscript-D9f6rSsU.js +1 -0
- package/dist/web/assets/velocity-CqftRsjg.js +1 -0
- package/dist/web/assets/{vennDiagram-UO4OBE2U-gjloIQdf.js → vennDiagram-UO4OBE2U-DBjzpbFr.js} +1 -1
- package/dist/web/assets/verilog-CjI9Y6RI.js +1 -0
- package/dist/web/assets/vhdl-kS471soi.js +1 -0
- package/dist/web/assets/{wardley-WFR3VGLG-BnsYd7B2.js → wardley-WFR3VGLG-DmlbUWUM.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-ktflnFLZ.js → wardleyDiagram-VNRHLVJA-CWSZCqf4.js} +1 -1
- package/dist/web/assets/webidl-D5jiJvOU.js +1 -0
- package/dist/web/assets/window-store-DNnKUhxs.js +1 -0
- package/dist/web/assets/xquery-BKJx4mf9.js +1 -0
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-Ch5MYgz0.js → xychartDiagram-PMCCYNJV-CMnNSaZv.js} +1 -1
- package/dist/web/assets/yacas-CXXgtw0p.js +1 -0
- package/dist/web/assets/z80-Co3PbHdE.js +1 -0
- package/dist/web/index.html +6 -3
- package/package.json +10 -2
- package/dist/web/assets/AppPage-BGcEFW_o.js +0 -93
- package/dist/web/assets/CreateReviewFabButton-CdUXVriU.js +0 -1
- package/dist/web/assets/IndexPage-CYFIGWy9.css +0 -1
- package/dist/web/assets/IndexPage-OqgW0yH8.js +0 -1
- package/dist/web/assets/MainLayout-Bi0JE3ER.js +0 -11
- package/dist/web/assets/MainLayout-DPkSlqMr.css +0 -1
- package/dist/web/assets/QBtn-CzrOO1zA.js +0 -1
- package/dist/web/assets/QScrollArea-C4BKutrt.js +0 -1
- package/dist/web/assets/QSelect-BXUaq4ky.js +0 -1
- package/dist/web/assets/QSpace-BMpRu3a-.js +0 -13
- package/dist/web/assets/ReviewDetailPage-DlR7Vxkw.js +0 -1
- package/dist/web/assets/ReviewsPage-DM8XtlUK.js +0 -1
- package/dist/web/assets/StatusSwitcher-Blg5JmAa.js +0 -1
- package/dist/web/assets/agent-store-CDuiBhST.js +0 -1
- package/dist/web/assets/app-info-store-BmD_P8q2.js +0 -1
- package/dist/web/assets/architecture-7GRP2DOG-DMgDJZyQ.js +0 -1
- package/dist/web/assets/channel-BnUyOMnk.js +0 -1
- package/dist/web/assets/core-C37KF-j0.js +0 -12
- package/dist/web/assets/dagre-6A5THRUB-Bk3Po5o5.js +0 -4
- package/dist/web/assets/eventmodeling-NTZA5JFV-7YdTAmVS.js +0 -1
- package/dist/web/assets/flowDiagram-KWPJA3E3-eOIiY2oF.js +0 -1
- package/dist/web/assets/graphlib-DS17s2tU.js +0 -1
- package/dist/web/assets/i18n-C5Xnmkwe.js +0 -1
- package/dist/web/assets/index-BqedItFI.js +0 -2
- package/dist/web/assets/pinia-BKoxNmoW.js +0 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-DZSn8MK2.js +0 -1
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-BVTJtDBq.js +0 -1
- package/dist/web/assets/railroad-peg-WCYAUIDC-DMzsNGzj.js +0 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-BxXXHQ70.js +0 -8
- package/dist/web/assets/theme-CPoPdFci.js +0 -1
- package/dist/web/assets/use-panel-width-C9dWftsJ.js +0 -1
- package/dist/web/assets/use-server-events-znzHKZZx.js +0 -1
- package/dist/web/assets/use-virtual-scroll-CIwaJCUj.js +0 -1
|
@@ -42,15 +42,18 @@ export class TerminalRegistry {
|
|
|
42
42
|
}
|
|
43
43
|
get(id) {
|
|
44
44
|
const entry = this.entries.get(id);
|
|
45
|
-
if (!entry)
|
|
45
|
+
if (!entry) {
|
|
46
46
|
throw new TerminalNotFoundError('Terminal session no longer exists.');
|
|
47
|
+
}
|
|
47
48
|
return entry.session;
|
|
48
49
|
}
|
|
49
50
|
async create(cols, rows) {
|
|
50
|
-
if (this.stopped)
|
|
51
|
+
if (this.stopped) {
|
|
51
52
|
throw new TerminalNotFoundError('Terminal server is stopping.');
|
|
52
|
-
|
|
53
|
+
}
|
|
54
|
+
if (this.entries.size + this.creating >= MAX_TERMINAL_SESSIONS) {
|
|
53
55
|
throw new TerminalLimitError('The terminal limit has been reached.');
|
|
56
|
+
}
|
|
54
57
|
this.creating++;
|
|
55
58
|
try {
|
|
56
59
|
const backend = await (this.options.backend ?? createBackend)(this.options.repositoryPath, cols, rows);
|
|
@@ -78,16 +81,18 @@ export class TerminalRegistry {
|
|
|
78
81
|
}
|
|
79
82
|
attach(id, subscriber) {
|
|
80
83
|
const entry = this.entries.get(id);
|
|
81
|
-
if (!entry)
|
|
84
|
+
if (!entry) {
|
|
82
85
|
throw new TerminalNotFoundError('Terminal session no longer exists.');
|
|
86
|
+
}
|
|
83
87
|
entry.subscribers.add(subscriber);
|
|
84
88
|
entry.orphanedSince = null;
|
|
85
89
|
entry.idleSince = null;
|
|
86
90
|
}
|
|
87
91
|
detach(subscriber) {
|
|
88
92
|
for (const entry of this.entries.values()) {
|
|
89
|
-
if (!entry.subscribers.delete(subscriber) || entry.subscribers.size)
|
|
93
|
+
if (!entry.subscribers.delete(subscriber) || entry.subscribers.size) {
|
|
90
94
|
continue;
|
|
95
|
+
}
|
|
91
96
|
entry.orphanedSince = this.now();
|
|
92
97
|
entry.idleSince = entry.session.running === 'idle' ? this.now() : null;
|
|
93
98
|
}
|
|
@@ -99,8 +99,9 @@ export class TerminalSession {
|
|
|
99
99
|
let end = Math.min(data.length, offset + FRAME_PART_UNITS);
|
|
100
100
|
if (end < data.length &&
|
|
101
101
|
data.charCodeAt(end - 1) >= HIGH_SURROGATE_FIRST &&
|
|
102
|
-
data.charCodeAt(end - 1) <= HIGH_SURROGATE_LAST)
|
|
102
|
+
data.charCodeAt(end - 1) <= HIGH_SURROGATE_LAST) {
|
|
103
103
|
end--;
|
|
104
|
+
}
|
|
104
105
|
const part = data.slice(offset, end);
|
|
105
106
|
offset = end;
|
|
106
107
|
const bytes = Buffer.byteLength(part);
|
|
@@ -131,8 +132,9 @@ export class TerminalSession {
|
|
|
131
132
|
.catch(() => this.onExit())
|
|
132
133
|
.finally(() => {
|
|
133
134
|
this.pendingBytes -= bytes;
|
|
134
|
-
if (!this.stopped && this.pendingBytes < QUEUE_RESUME_BYTES)
|
|
135
|
+
if (!this.stopped && this.pendingBytes < QUEUE_RESUME_BYTES) {
|
|
135
136
|
this.updateFlow();
|
|
137
|
+
}
|
|
136
138
|
});
|
|
137
139
|
}
|
|
138
140
|
}
|
|
@@ -167,8 +169,9 @@ export class TerminalSession {
|
|
|
167
169
|
scrollback = Math.floor(scrollback / 2);
|
|
168
170
|
data = this.serializer.serialize({ scrollback }) + this.tail.tail;
|
|
169
171
|
}
|
|
170
|
-
if (Buffer.byteLength(data) > TERMINAL_SNAPSHOT_BYTES)
|
|
172
|
+
if (Buffer.byteLength(data) > TERMINAL_SNAPSHOT_BYTES) {
|
|
171
173
|
throw new TerminalError('Terminal snapshot exceeds its limit.');
|
|
174
|
+
}
|
|
172
175
|
send({
|
|
173
176
|
type: 'snapshot',
|
|
174
177
|
terminalId: this.id,
|
|
@@ -186,8 +189,9 @@ export class TerminalSession {
|
|
|
186
189
|
this.backend.write(data);
|
|
187
190
|
}
|
|
188
191
|
line(data) {
|
|
189
|
-
if (this.backend.mode !== 'pipe')
|
|
192
|
+
if (this.backend.mode !== 'pipe') {
|
|
190
193
|
throw new TerminalError('Line input requires limited mode.');
|
|
194
|
+
}
|
|
191
195
|
this.output(data.replace(/\n/g, '\r\n') + '\r\n');
|
|
192
196
|
this.backend.write(data + '\n');
|
|
193
197
|
}
|
|
@@ -203,8 +207,9 @@ export class TerminalSession {
|
|
|
203
207
|
this.flush();
|
|
204
208
|
this.queue = this.queue
|
|
205
209
|
.then(() => {
|
|
206
|
-
if (this.stopped || (cols === this.cols && rows === this.rows))
|
|
210
|
+
if (this.stopped || (cols === this.cols && rows === this.rows)) {
|
|
207
211
|
return undefined;
|
|
212
|
+
}
|
|
208
213
|
this.backend.resize(cols, rows);
|
|
209
214
|
this.terminal.resize(cols, rows);
|
|
210
215
|
this.cols = cols;
|
|
@@ -10,8 +10,9 @@ export class TerminalTokens {
|
|
|
10
10
|
}
|
|
11
11
|
issue(origin) {
|
|
12
12
|
this.prune();
|
|
13
|
-
if (this.tokens.size >= MAX_TOKENS)
|
|
13
|
+
if (this.tokens.size >= MAX_TOKENS) {
|
|
14
14
|
this.tokens.delete(this.tokens.keys().next().value ?? '');
|
|
15
|
+
}
|
|
15
16
|
const token = randomBytes(32).toString('base64url');
|
|
16
17
|
this.tokens.set(token, { origin, expires: this.now() + TERMINAL_TOKEN_MS });
|
|
17
18
|
return token;
|
|
@@ -22,9 +23,10 @@ export class TerminalTokens {
|
|
|
22
23
|
return !!info && info.expires > this.now() && info.origin === origin;
|
|
23
24
|
}
|
|
24
25
|
prune() {
|
|
25
|
-
for (const [token, info] of this.tokens)
|
|
26
|
+
for (const [token, info] of this.tokens) {
|
|
26
27
|
if (info.expires <= this.now())
|
|
27
28
|
this.tokens.delete(token);
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
clear() {
|
|
30
32
|
this.tokens.clear();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
2
|
+
import { NOTE_ASSET_FORMATS, SUPPORTED_NOTE_ASSETS } from "../../shared/notes/asset-formats.js";
|
|
3
|
+
import { BadRequestError } from "../errors/http.js";
|
|
4
|
+
export async function detectNoteAssetFormat(bytes) {
|
|
5
|
+
let detected;
|
|
6
|
+
try {
|
|
7
|
+
detected = await fileTypeFromBuffer(bytes);
|
|
8
|
+
}
|
|
9
|
+
catch (cause) {
|
|
10
|
+
throw new BadRequestError('Could not identify attachment format', { cause });
|
|
11
|
+
}
|
|
12
|
+
const format = detected &&
|
|
13
|
+
NOTE_ASSET_FORMATS.find(candidate => candidate.extension === detected.ext ||
|
|
14
|
+
candidate.aliases.some(alias => alias === detected.ext));
|
|
15
|
+
if (!format) {
|
|
16
|
+
throw new BadRequestError('Supported attachments: ' + SUPPORTED_NOTE_ASSETS);
|
|
17
|
+
}
|
|
18
|
+
return format;
|
|
19
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { lookup } from 'node:dns/promises';
|
|
2
|
+
import { BlockList, isIP } from 'node:net';
|
|
3
|
+
import { Agent, request } from 'undici';
|
|
4
|
+
import { BadRequestError } from "../errors/http.js";
|
|
5
|
+
import { isCredentialFreeWebUrl } from "../../shared/utils/web-url.js";
|
|
6
|
+
// Stop connection attempts that cannot establish a socket promptly.
|
|
7
|
+
const CONNECTION_TIMEOUT_MS = 10000;
|
|
8
|
+
// Stop responses that send no headers or no body data.
|
|
9
|
+
const RESPONSE_IDLE_TIMEOUT_MS = 15000;
|
|
10
|
+
// Bound the size of response headers.
|
|
11
|
+
const MAX_HEADER_BYTES = 16384;
|
|
12
|
+
// Stop redirect chains and loops.
|
|
13
|
+
const MAX_REDIRECTS = 5;
|
|
14
|
+
// Only these statuses ask the client to follow a Location header.
|
|
15
|
+
const REDIRECT_STATUSES = [301, 302, 303, 307, 308];
|
|
16
|
+
// Network failures that mean the remote server cannot serve the attachment.
|
|
17
|
+
const UNREACHABLE_CODES = new Set([
|
|
18
|
+
'ENOTFOUND',
|
|
19
|
+
'ECONNREFUSED',
|
|
20
|
+
'ECONNRESET',
|
|
21
|
+
'EHOSTUNREACH',
|
|
22
|
+
'ENETUNREACH',
|
|
23
|
+
'ETIMEDOUT',
|
|
24
|
+
'UND_ERR_SOCKET'
|
|
25
|
+
]);
|
|
26
|
+
// Ask servers for the exact bytes: compressed bodies are not supported.
|
|
27
|
+
const REQUEST_HEADERS = {
|
|
28
|
+
'accept-encoding': 'identity',
|
|
29
|
+
'user-agent': 'githuman-note-assets'
|
|
30
|
+
};
|
|
31
|
+
// Exclude non-public IPv4 ranges before opening any socket.
|
|
32
|
+
const BLOCKED_IPV4 = new BlockList();
|
|
33
|
+
// IPv6 routing must be public and must exclude special-purpose ranges.
|
|
34
|
+
const PUBLIC_IPV6 = new BlockList();
|
|
35
|
+
const BLOCKED_IPV6 = new BlockList();
|
|
36
|
+
for (const [address, prefix] of [
|
|
37
|
+
['0.0.0.0', 8],
|
|
38
|
+
['10.0.0.0', 8],
|
|
39
|
+
['100.64.0.0', 10],
|
|
40
|
+
['127.0.0.0', 8],
|
|
41
|
+
['169.254.0.0', 16],
|
|
42
|
+
['172.16.0.0', 12],
|
|
43
|
+
['192.0.0.0', 24],
|
|
44
|
+
['192.0.2.0', 24],
|
|
45
|
+
['192.88.99.0', 24],
|
|
46
|
+
['192.168.0.0', 16],
|
|
47
|
+
['198.18.0.0', 15],
|
|
48
|
+
['198.51.100.0', 24],
|
|
49
|
+
['203.0.113.0', 24],
|
|
50
|
+
['224.0.0.0', 3]
|
|
51
|
+
]) {
|
|
52
|
+
BLOCKED_IPV4.addSubnet(address, prefix, 'ipv4');
|
|
53
|
+
}
|
|
54
|
+
// Only globally routable IPv6 is eligible, with special-purpose ranges excluded.
|
|
55
|
+
PUBLIC_IPV6.addSubnet('2000::', 3, 'ipv6');
|
|
56
|
+
BLOCKED_IPV6.addSubnet('2001::', 23, 'ipv6');
|
|
57
|
+
BLOCKED_IPV6.addSubnet('2001:db8::', 32, 'ipv6');
|
|
58
|
+
BLOCKED_IPV6.addSubnet('2002::', 16, 'ipv6');
|
|
59
|
+
BLOCKED_IPV6.addSubnet('3fff::', 20, 'ipv6');
|
|
60
|
+
export function isPublicAssetAddress(address) {
|
|
61
|
+
const family = isIP(address);
|
|
62
|
+
if (family === 4)
|
|
63
|
+
return !BLOCKED_IPV4.check(address, 'ipv4');
|
|
64
|
+
return (family === 6 &&
|
|
65
|
+
PUBLIC_IPV6.check(address, 'ipv6') &&
|
|
66
|
+
!BLOCKED_IPV6.check(address, 'ipv6'));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolves a host name and refuses non-public results. The socket connects to
|
|
70
|
+
* the checked address, so no second lookup can change the target.
|
|
71
|
+
*/
|
|
72
|
+
export function createPublicLookup(resolveHost = lookup) {
|
|
73
|
+
return (hostname, options, callback) => {
|
|
74
|
+
void (async () => {
|
|
75
|
+
let addresses;
|
|
76
|
+
try {
|
|
77
|
+
addresses = await resolveHost(hostname, { all: true, verbatim: true });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
callback(error instanceof Error ? error : new Error(String(error)), '');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const first = addresses[0];
|
|
84
|
+
if (!first ||
|
|
85
|
+
addresses.some(item => !isPublicAssetAddress(item.address))) {
|
|
86
|
+
callback(new BadRequestError('Attachment address is not public'), '');
|
|
87
|
+
}
|
|
88
|
+
else if (options.all) {
|
|
89
|
+
callback(null, addresses);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
callback(null, first.address, first.family);
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function createAgent(resolveHost) {
|
|
98
|
+
return new Agent({
|
|
99
|
+
connect: {
|
|
100
|
+
lookup: createPublicLookup(resolveHost),
|
|
101
|
+
timeout: CONNECTION_TIMEOUT_MS
|
|
102
|
+
},
|
|
103
|
+
connectTimeout: CONNECTION_TIMEOUT_MS,
|
|
104
|
+
headersTimeout: RESPONSE_IDLE_TIMEOUT_MS,
|
|
105
|
+
bodyTimeout: RESPONSE_IDLE_TIMEOUT_MS,
|
|
106
|
+
maxHeaderSize: MAX_HEADER_BYTES
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Undici ignores an abort while it waits for a socket, so the wait ends here.
|
|
110
|
+
async function untilAborted(work, signal, discard) {
|
|
111
|
+
let abort;
|
|
112
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
113
|
+
abort = () => reject(signal.reason);
|
|
114
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
return await Promise.race([work, aborted]);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// A request that finishes after the abort still owns a socket.
|
|
121
|
+
work.then(discard, () => undefined);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (abort)
|
|
126
|
+
signal.removeEventListener('abort', abort);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let sharedAgent;
|
|
130
|
+
function parseAssetUrl(value, message, base) {
|
|
131
|
+
let url;
|
|
132
|
+
try {
|
|
133
|
+
url = new URL(value, base);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new BadRequestError(message);
|
|
137
|
+
}
|
|
138
|
+
if (!isCredentialFreeWebUrl(url)) {
|
|
139
|
+
throw new BadRequestError('Only public HTTP and HTTPS attachments are supported');
|
|
140
|
+
}
|
|
141
|
+
return url;
|
|
142
|
+
}
|
|
143
|
+
export async function remoteAssetRequest(source, method, signal, options = {}) {
|
|
144
|
+
const dispatcher = options.dispatcher ??
|
|
145
|
+
(options.resolveHost
|
|
146
|
+
? createAgent(options.resolveHost)
|
|
147
|
+
: (sharedAgent ??= createAgent()));
|
|
148
|
+
let url = parseAssetUrl(source, 'Invalid attachment URL');
|
|
149
|
+
for (let redirects = 0;; redirects++) {
|
|
150
|
+
signal.throwIfAborted();
|
|
151
|
+
// The socket layer skips the lookup for IP literals, so check them here.
|
|
152
|
+
const host = url.hostname.replace(/^\[|\]$/g, '');
|
|
153
|
+
if (isIP(host) && !isPublicAssetAddress(host)) {
|
|
154
|
+
throw new BadRequestError('Attachment address is not public');
|
|
155
|
+
}
|
|
156
|
+
const { statusCode, headers, body } = await untilAborted(request(url, { method, signal, dispatcher, headers: REQUEST_HEADERS }), signal, late => late.body.destroy());
|
|
157
|
+
if (!REDIRECT_STATUSES.includes(statusCode)) {
|
|
158
|
+
return Object.assign(body, { statusCode, headers });
|
|
159
|
+
}
|
|
160
|
+
body.destroy();
|
|
161
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
162
|
+
throw new BadRequestError('Too many attachment redirects');
|
|
163
|
+
}
|
|
164
|
+
const location = headers.location;
|
|
165
|
+
if (!location || Array.isArray(location)) {
|
|
166
|
+
throw new BadRequestError('Attachment redirect has no valid location');
|
|
167
|
+
}
|
|
168
|
+
const next = parseAssetUrl(location, 'Invalid attachment redirect', url);
|
|
169
|
+
if (url.protocol === 'https:' && next.protocol !== 'https:') {
|
|
170
|
+
throw new BadRequestError('Insecure attachment redirect');
|
|
171
|
+
}
|
|
172
|
+
url = next;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Maps transport failures to client errors; other errors pass through. */
|
|
176
|
+
export function translateRemoteAssetError(error) {
|
|
177
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
178
|
+
? error.code
|
|
179
|
+
: undefined;
|
|
180
|
+
if (error instanceof DOMException && error.name === 'TimeoutError') {
|
|
181
|
+
return new BadRequestError('Attachment download took too long');
|
|
182
|
+
}
|
|
183
|
+
if (code === 'UND_ERR_CONNECT_TIMEOUT') {
|
|
184
|
+
return new BadRequestError('Attachment connection timed out');
|
|
185
|
+
}
|
|
186
|
+
if (code === 'UND_ERR_HEADERS_TIMEOUT' || code === 'UND_ERR_BODY_TIMEOUT') {
|
|
187
|
+
return new BadRequestError('Attachment download stalled');
|
|
188
|
+
}
|
|
189
|
+
if (typeof code === 'string' && UNREACHABLE_CODES.has(code)) {
|
|
190
|
+
return new BadRequestError('Attachment server could not be reached');
|
|
191
|
+
}
|
|
192
|
+
return error;
|
|
193
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `work` with a signal that aborts when the client closes the connection
|
|
3
|
+
* before the response ends. A normal finish does not abort the signal.
|
|
4
|
+
*/
|
|
5
|
+
export async function withRequestSignal(reply, work) {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const abort = () => {
|
|
8
|
+
if (!reply.raw.writableFinished)
|
|
9
|
+
controller.abort();
|
|
10
|
+
};
|
|
11
|
+
if (reply.raw.destroyed)
|
|
12
|
+
abort();
|
|
13
|
+
reply.raw.once('close', abort);
|
|
14
|
+
try {
|
|
15
|
+
return await work(controller.signal);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
reply.raw.removeListener('close', abort);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function hasErrorCode(error, ...codes) {
|
|
2
|
+
return (error !== null &&
|
|
3
|
+
typeof error === 'object' &&
|
|
4
|
+
'code' in error &&
|
|
5
|
+
typeof error.code === 'string' &&
|
|
6
|
+
codes.includes(error.code));
|
|
7
|
+
}
|
|
8
|
+
export function isSystemError(error, code) {
|
|
9
|
+
return error instanceof Error && hasErrorCode(error, code);
|
|
10
|
+
}
|
|
11
|
+
// Git file reads treat a non-directory path component as a missing path.
|
|
12
|
+
export function isMissingPathError(error) {
|
|
13
|
+
return hasErrorCode(error, 'ENOENT', 'ENOTDIR');
|
|
14
|
+
}
|
|
@@ -37,6 +37,20 @@ export const FileSearchQuery = Type.Object({
|
|
|
37
37
|
}))
|
|
38
38
|
});
|
|
39
39
|
export const ContextItem = Type.Union([
|
|
40
|
+
Type.Object({
|
|
41
|
+
kind: Type.Literal('note', { description: 'A project note.' }),
|
|
42
|
+
noteId: Type.String({ minLength: 1, description: 'Stable note UUID.' }),
|
|
43
|
+
storageId: Type.String({
|
|
44
|
+
minLength: 1,
|
|
45
|
+
description: 'Notes storage identity.'
|
|
46
|
+
}),
|
|
47
|
+
name: Type.String({
|
|
48
|
+
minLength: 1,
|
|
49
|
+
description: 'Display name when attached.'
|
|
50
|
+
})
|
|
51
|
+
}, {
|
|
52
|
+
description: 'The current Markdown of a note, including YAML properties.'
|
|
53
|
+
}),
|
|
40
54
|
Type.Object({
|
|
41
55
|
kind: Type.Literal('diff', { description: 'Live working-tree diff.' }),
|
|
42
56
|
source: DiffSourceNameSchema,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { MAX_NOTE_ASSET_BYTES, MAX_NOTE_PDF_BYTES } from "./constants.js";
|
|
2
|
+
// Keep upload policy, response MIME types and asset identifiers in agreement.
|
|
3
|
+
export const NOTE_ASSET_FORMATS = [
|
|
4
|
+
// Animated PNG retains the same storage extension as existing PNG attachments.
|
|
5
|
+
{
|
|
6
|
+
extension: 'png',
|
|
7
|
+
mime: 'image/png',
|
|
8
|
+
label: 'PNG',
|
|
9
|
+
display: 'image',
|
|
10
|
+
maxBytes: MAX_NOTE_ASSET_BYTES,
|
|
11
|
+
aliases: ['apng']
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
extension: 'jpg',
|
|
15
|
+
mime: 'image/jpeg',
|
|
16
|
+
label: 'JPEG',
|
|
17
|
+
display: 'image',
|
|
18
|
+
maxBytes: MAX_NOTE_ASSET_BYTES,
|
|
19
|
+
aliases: ['jpeg']
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
extension: 'gif',
|
|
23
|
+
mime: 'image/gif',
|
|
24
|
+
label: 'GIF',
|
|
25
|
+
display: 'image',
|
|
26
|
+
maxBytes: MAX_NOTE_ASSET_BYTES,
|
|
27
|
+
aliases: []
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
extension: 'webp',
|
|
31
|
+
mime: 'image/webp',
|
|
32
|
+
label: 'WebP',
|
|
33
|
+
display: 'image',
|
|
34
|
+
maxBytes: MAX_NOTE_ASSET_BYTES,
|
|
35
|
+
aliases: []
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
extension: 'pdf',
|
|
39
|
+
mime: 'application/pdf',
|
|
40
|
+
label: 'PDF',
|
|
41
|
+
display: 'link',
|
|
42
|
+
maxBytes: MAX_NOTE_PDF_BYTES,
|
|
43
|
+
aliases: []
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
// Existing content hashes and extensions remain valid across server upgrades.
|
|
47
|
+
export const NOTE_ASSET_ID_PATTERN = `^[a-f0-9]{64}\\.(${NOTE_ASSET_FORMATS.map(format => format.extension).join('|')})$`;
|
|
48
|
+
// Error messages follow the same allowlist as upload validation.
|
|
49
|
+
export const SUPPORTED_NOTE_ASSETS = NOTE_ASSET_FORMATS.map(format => format.label).join(', ');
|
|
50
|
+
// Extensions that identify an image attachment, including accepted aliases.
|
|
51
|
+
export const NOTE_IMAGE_EXTENSIONS = NOTE_ASSET_FORMATS.filter(format => format.display === 'image').flatMap(format => [format.extension, ...format.aliases]);
|
|
52
|
+
export function noteAssetFormat(extension) {
|
|
53
|
+
return NOTE_ASSET_FORMATS.find(format => format.extension === extension);
|
|
54
|
+
}
|
|
55
|
+
export function maxNoteAssetBytes(kind) {
|
|
56
|
+
return kind === 'pdf' ? MAX_NOTE_PDF_BYTES : MAX_NOTE_ASSET_BYTES;
|
|
57
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Bound image uploads in both the browser and the multipart parser.
|
|
2
|
+
export const MAX_NOTE_ASSET_BYTES = 10_000_000;
|
|
3
|
+
// PDF imports require confirmation above the smaller threshold.
|
|
4
|
+
export const NOTE_PDF_WARNING_BYTES = 50_000_000;
|
|
5
|
+
export const MAX_NOTE_PDF_BYTES = 100_000_000;
|
|
6
|
+
// This bounds useful downloads in one client operation.
|
|
7
|
+
export const MAX_NOTE_IMPORT_BYTES = 200_000_000;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { isRecord } from "../utils/guards.js";
|
|
2
|
+
import { errorMessage } from "../utils/error-message.js";
|
|
3
|
+
import { isMap, isSeq, isScalar, parseDocument } from 'yaml';
|
|
4
|
+
export function splitNote(text) {
|
|
5
|
+
const bom = text.startsWith('\uFEFF') ? '\uFEFF' : '';
|
|
6
|
+
const source = text.slice(bom.length);
|
|
7
|
+
if (!/^---\r?\n/.test(source)) {
|
|
8
|
+
return {
|
|
9
|
+
head: bom,
|
|
10
|
+
body: source,
|
|
11
|
+
yaml: '',
|
|
12
|
+
eol: text.includes('\r\n') ? '\r\n' : '\n'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const match = source.match(/^---\r?\n([\s\S]*?)(?:^---[ \t]*(?:\r?\n|$))/m);
|
|
16
|
+
if (!match)
|
|
17
|
+
throw new Error('Unclosed YAML frontmatter');
|
|
18
|
+
return {
|
|
19
|
+
head: bom + match[0],
|
|
20
|
+
body: source.slice(match[0].length),
|
|
21
|
+
yaml: match[1] ?? '',
|
|
22
|
+
eol: match[0].includes('\r\n') ? '\r\n' : '\n'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function noteMetadata(text) {
|
|
26
|
+
try {
|
|
27
|
+
const { doc } = parseNoteHeader(text);
|
|
28
|
+
const tags = doc.get('tags');
|
|
29
|
+
if (tags === undefined)
|
|
30
|
+
return { tags: [], error: null };
|
|
31
|
+
if (!isSeq(tags) ||
|
|
32
|
+
tags.items.some(item => !isScalar(item) || typeof item.value !== 'string')) {
|
|
33
|
+
throw new Error('tags must be a list of strings');
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
tags: normalizeTags(tags.items.map(item => (isScalar(item) ? String(item.value) : ''))),
|
|
37
|
+
error: null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
tags: [],
|
|
43
|
+
error: errorMessage(error)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function normalizeTags(tags) {
|
|
48
|
+
return [...new Set(tags.map(tag => tag.trim()).filter(Boolean))];
|
|
49
|
+
}
|
|
50
|
+
export function setNoteTags(text, tags) {
|
|
51
|
+
const metadata = noteMetadata(text);
|
|
52
|
+
if (metadata.error)
|
|
53
|
+
throw new Error(metadata.error);
|
|
54
|
+
const next = normalizeTags(tags);
|
|
55
|
+
if (JSON.stringify(next) === JSON.stringify(metadata.tags))
|
|
56
|
+
return text;
|
|
57
|
+
const { parts, doc } = parseNoteHeader(text);
|
|
58
|
+
doc.set('tags', next);
|
|
59
|
+
return writeNoteHeader(text, parts, doc.toString());
|
|
60
|
+
}
|
|
61
|
+
function parseNoteHeader(text) {
|
|
62
|
+
const parts = splitNote(text);
|
|
63
|
+
const doc = parseDocument(parts.yaml);
|
|
64
|
+
if (doc.errors.length)
|
|
65
|
+
throw new Error(doc.errors[0]?.message);
|
|
66
|
+
if (doc.contents && !isMap(doc.contents)) {
|
|
67
|
+
throw new Error('Frontmatter must be a YAML mapping');
|
|
68
|
+
}
|
|
69
|
+
return { parts, doc };
|
|
70
|
+
}
|
|
71
|
+
export function noteProperties(text) {
|
|
72
|
+
const { doc } = parseNoteHeader(text);
|
|
73
|
+
if (!isMap(doc.contents))
|
|
74
|
+
return [];
|
|
75
|
+
const values = doc.toJS({ maxAliasCount: 100 });
|
|
76
|
+
if (!isRecord(values))
|
|
77
|
+
return [];
|
|
78
|
+
return doc.contents.items.map(item => {
|
|
79
|
+
if (!isScalar(item.key) || typeof item.key.value !== 'string') {
|
|
80
|
+
throw new Error('Property names must be strings');
|
|
81
|
+
}
|
|
82
|
+
const key = item.key.value;
|
|
83
|
+
return { key, value: values[key] };
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function writeNoteHeader(text, parts, yaml) {
|
|
87
|
+
const bom = text.startsWith('\uFEFF') ? '\uFEFF' : '';
|
|
88
|
+
return (bom +
|
|
89
|
+
'---' +
|
|
90
|
+
parts.eol +
|
|
91
|
+
yaml.replace(/\n/g, parts.eol) +
|
|
92
|
+
'---' +
|
|
93
|
+
parts.eol +
|
|
94
|
+
parts.body);
|
|
95
|
+
}
|
|
96
|
+
export function setNoteProperty(text, key, value) {
|
|
97
|
+
if (!key.trim())
|
|
98
|
+
throw new Error('Property name must not be empty');
|
|
99
|
+
if (key === 'tags') {
|
|
100
|
+
if (!Array.isArray(value))
|
|
101
|
+
throw new Error('tags must be a list of strings');
|
|
102
|
+
return setNoteTags(text, value);
|
|
103
|
+
}
|
|
104
|
+
const { parts, doc } = parseNoteHeader(text);
|
|
105
|
+
const current = noteProperties(text).find(property => property.key === key);
|
|
106
|
+
if (current && JSON.stringify(current.value) === JSON.stringify(value)) {
|
|
107
|
+
return text;
|
|
108
|
+
}
|
|
109
|
+
doc.set(key, value);
|
|
110
|
+
return writeNoteHeader(text, parts, doc.toString());
|
|
111
|
+
}
|
|
112
|
+
export function removeNoteProperty(text, key) {
|
|
113
|
+
const { parts, doc } = parseNoteHeader(text);
|
|
114
|
+
if (!doc.has(key))
|
|
115
|
+
return text;
|
|
116
|
+
doc.delete(key);
|
|
117
|
+
return writeNoteHeader(text, parts, doc.toString());
|
|
118
|
+
}
|